AAE-40269 Add storybook v10 (#11401)

This commit is contained in:
Wojciech Duda
2026-01-09 18:17:43 +00:00
committed by GitHub
parent f0c6cc0ff7
commit 5f9de1fde8
119 changed files with 11010 additions and 753 deletions
@@ -0,0 +1,68 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { applicationConfig, Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { provideStoryProcessServicesCloud } from '../../stories/process-services-cloud-story.providers';
import { mockFilterProperty } from '../mock/date-range-filter.mock';
import { DateRangeFilterComponent } from './date-range-filter.component';
const meta: Meta<DateRangeFilterComponent> = {
component: DateRangeFilterComponent,
title: 'Process Services Cloud/Process Common/Date Range Filter',
decorators: [
moduleMetadata({
imports: [DateRangeFilterComponent]
}),
applicationConfig({
providers: [...provideStoryProcessServicesCloud()]
})
],
argTypes: {
processFilterProperty: {
control: 'object',
table: {
type: { summary: 'ApplicationInstanceModel' }
}
},
options: {
control: 'object',
table: {
type: { summary: 'DateCloudFilterType[]' }
}
},
dateChanged: {
action: 'dateChanged',
table: { category: 'Actions' }
},
dateTypeChange: {
action: 'dateTypeChange',
table: { category: 'Actions' }
}
},
args: {
processFilterProperty: mockFilterProperty
}
};
export default meta;
type Story = StoryObj<DateRangeFilterComponent>;
export const DateRangeFilter: Story = {
render: (args) => ({
props: args
})
};
@@ -64,19 +64,16 @@ $overlapping-lt: (
@if map.has-key($breakpoints, $bp) {
$min: map.get(map.get($breakpoints, $bp), begin);
$max: map.get(map.get($breakpoints, $bp), end);
@media (min-width: $min) and (max-width: $max) {
@content;
}
} @else if map.has-key($overlapping-gt, $bp) {
$min: map.get($overlapping-gt, $bp);
@media (min-width: $min) {
@content;
}
} @else if map.has-key($overlapping-lt, $bp) {
$max: map.get($overlapping-lt, $bp);
@media (max-width: $max) {
@content;
}
@@ -60,7 +60,7 @@ export const cloudFormMock = {
id: 'text2',
name: 'Text2',
type: 'text',
value: null,
value: '',
required: false,
readOnly: true,
overrideId: false,
@@ -147,6 +147,7 @@ export const cloudFormMock = {
overrideId: false,
name: 'Number1',
type: 'integer',
value: null,
colspan: 1,
placeholder: null,
readOnly: true,
@@ -240,7 +241,7 @@ export const cloudFormMock = {
id: 'dropdown1',
name: 'Dropdown1',
type: 'dropdown',
value: null,
value: '',
required: false,
readOnly: true,
overrideId: false,
@@ -267,7 +268,7 @@ export const cloudFormMock = {
id: 'dropdown2',
name: 'Dropdown2',
type: 'dropdown',
value: null,
value: '',
required: false,
readOnly: true,
overrideId: false,
@@ -304,7 +305,7 @@ export const cloudFormMock = {
id: 'amount1',
name: 'Amount1',
type: 'amount',
value: null,
value: '',
required: false,
readOnly: true,
overrideId: false,
@@ -327,7 +328,7 @@ export const cloudFormMock = {
id: 'amount2',
name: 'Amount2',
type: 'amount',
value: null,
value: '',
required: false,
readOnly: true,
overrideId: false,
@@ -412,7 +413,7 @@ export const cloudFormMock = {
id: 'radiobuttons1',
name: 'Radio buttons1',
type: 'radio-buttons',
value: null,
value: '',
required: false,
readOnly: true,
overrideId: false,
@@ -448,7 +449,7 @@ export const cloudFormMock = {
id: 'radiobuttons2',
name: 'Radio buttons2',
type: 'radio-buttons',
value: null,
value: '',
required: false,
readOnly: true,
overrideId: false,
@@ -494,7 +495,7 @@ export const cloudFormMock = {
id: 'attachfile1',
name: 'Attach file1',
type: 'upload',
value: null,
value: [],
required: false,
readOnly: true,
overrideId: false,
@@ -519,7 +520,7 @@ export const cloudFormMock = {
id: 'attachfile2',
name: 'Attach file2',
type: 'upload',
value: null,
value: [],
required: false,
readOnly: true,
overrideId: false,
@@ -704,7 +705,7 @@ export const fakeCloudForm = {
},
visibilityCondition: null,
placeholder: null,
value: null,
value: '',
required: false,
minLength: 0,
maxLength: 0,
@@ -723,7 +724,7 @@ export const fakeCloudForm = {
},
visibilityCondition: null,
placeholder: null,
value: null,
value: '',
required: false,
minLength: 0,
maxLength: 0,
@@ -1076,7 +1077,7 @@ export const fakeMetadataForm = {
},
visibilityCondition: null,
placeholder: null,
value: null,
value: '',
required: false,
minLength: 0,
maxLength: 0,
@@ -1095,7 +1096,7 @@ export const fakeMetadataForm = {
},
visibilityCondition: null,
placeholder: null,
value: null,
value: '',
required: false,
minLength: 0,
maxLength: 0,
@@ -1263,7 +1264,7 @@ export const fakeMetadataForm = {
},
visibilityCondition: null,
placeholder: null,
value: null,
value: '',
required: false,
minLength: 0,
maxLength: 0,
@@ -1282,7 +1283,7 @@ export const fakeMetadataForm = {
},
visibilityCondition: null,
placeholder: null,
value: null,
value: '',
required: false,
minLength: 0,
maxLength: 0,
@@ -1301,7 +1302,7 @@ export const fakeMetadataForm = {
},
visibilityCondition: null,
placeholder: null,
value: null,
value: '',
required: false,
minLength: 0,
maxLength: 0,
@@ -0,0 +1,105 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormFieldOption, FormModel, FormValues } from '@alfresco/adf-core';
import { UploadApi } from '@alfresco/js-api';
import { Observable, of, throwError } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { FormContent } from '../../services/form-fields.interfaces';
import { TaskDetailsCloudModel } from '../../task/public-api';
import { taskDetailsContainer } from '../../task/task-header/mocks/task-details-cloud.mock';
import { fakeCloudForm } from '../mocks/cloud-form.mock';
import { TaskVariableCloud } from '../models/task-variable-cloud.model';
import { FormCloudServiceInterface } from '../services/form-cloud.service.interface';
export class FormCloudServiceMock implements FormCloudServiceInterface {
uploadApi: UploadApi;
getTaskForm(appName: string, taskId: string, version?: number): Observable<any> {
if (appName === 'invalid-app') {
return throwError(() => new Error('App Name not configured'));
}
return this.getTask(appName, taskId).pipe(
switchMap((task) =>
this.getForm(appName, task.formKey, version).pipe(
map((form: FormContent) => {
const flattenForm = {
...form.formRepresentation,
...form.formRepresentation.formDefinition,
taskId: task.id,
taskName: task.name,
processDefinitionId: task.processDefinitionId,
processInstanceId: task.processInstanceId
};
delete flattenForm.formDefinition;
return flattenForm;
})
)
)
);
}
getTask(_appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
return of(taskDetailsContainer[taskId]);
}
getForm(_appName: string, _formKey: string, _version?: number): Observable<FormContent> {
return of(fakeCloudForm);
}
getTaskVariables(_appName: string, _taskId: string): Observable<TaskVariableCloud[]> {
return of([new TaskVariableCloud({ name: 'name1', value: 5, type: 'text', id: '52' })]);
}
saveTaskForm(
_appName: string,
taskId: string,
_processInstanceId: string,
_formId: string,
_values: FormValues
): Observable<TaskDetailsCloudModel> {
return of(taskDetailsContainer[taskId]);
}
completeTaskForm(
_appName: string,
taskId: string,
_processInstanceId: string,
_formId: string,
_formValues: FormValues,
_outcome: string,
_version: number
): Observable<TaskDetailsCloudModel> {
return of(taskDetailsContainer[taskId]);
}
createTemporaryRawRelatedContent(_file: any, _nodeId: string, _contentHost: string): Observable<any> {
throw new Error('Method not implemented.');
}
getDropDownJsonData(_url: string): Observable<any> {
throw new Error('Method not implemented.');
}
parseForm(_json: any, _data?: TaskVariableCloud[], _readOnly: boolean = false): FormModel {
throw new Error('Method not implemented.');
}
getRestWidgetData(_formName: string, _widgetId: string, _body: Map<string, string>): Observable<FormFieldOption[]> {
throw new Error('Method not implemented.');
}
}
@@ -0,0 +1,194 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { applicationConfig, Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { GroupCloudComponent } from './group-cloud.component';
import { provideStoryProcessServicesCloud } from '../../stories/process-services-cloud-story.providers';
import { IdentityGroupService } from '../services/identity-group.service';
import { IdentityGroupServiceMock, mockFoodGroups, mockMeatChicken, mockVegetableAubergine } from '../mock/group-cloud.mock';
const meta: Meta<GroupCloudComponent> = {
component: GroupCloudComponent,
title: 'Process Services Cloud/Group Cloud/Group Cloud',
decorators: [
moduleMetadata({
imports: [GroupCloudComponent]
}),
applicationConfig({
providers: [{ provide: IdentityGroupService, useClass: IdentityGroupServiceMock }, ...provideStoryProcessServicesCloud()]
})
],
argTypes: {
appName: {
control: 'text',
description: 'Name of the application. If specified this shows the groups who have access to the app.',
table: {
type: { summary: 'string' }
}
},
title: {
control: 'text',
description: 'Title of the field.',
table: {
type: { summary: 'string' }
}
},
mode: {
control: 'radio',
options: ['single', 'multiple'],
description: 'Group selection mode.',
table: {
type: { summary: 'ComponentSelectionMode' },
defaultValue: { summary: 'single' }
}
},
preSelectGroups: {
control: 'object',
description:
'Array of groups to be pre-selected. This pre-selects all groups in multi selection mode and only the first group of the array in single selection mode.',
table: {
type: { summary: 'IdentityGroupModel[]' },
defaultValue: { summary: '[]' }
}
},
validate: {
control: 'boolean',
description:
'This flag enables the validation on the preSelectGroups passed as input.\n\n' +
'In case the flag is true the components call the identity service to verify the validity of the information passed as input.\n\n' +
'Otherwise, no check will be done.',
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' }
}
},
readOnly: {
control: 'boolean',
description: 'Show the info in readonly mode.',
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' }
}
},
required: {
control: 'boolean',
description: 'Mark this field as required.',
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' }
}
},
groupChipsCtrl: {
control: 'object',
description: 'FormControl to list of group.',
table: {
type: { summary: 'FormControl' },
defaultValue: { summary: "new FormControl({ value: '', disabled: false })" },
category: 'Form Controls'
}
},
searchGroupsControl: {
control: 'object',
description: 'FormControl to search the group.',
table: {
type: { summary: 'FormControl' },
defaultValue: { summary: "new FormControl({ value: '', disabled: false })" },
category: 'Form Controls'
}
},
roles: {
control: 'object',
description: 'Role names of the groups to be listed.',
table: {
type: { summary: 'string[]' },
defaultValue: { summary: '[]' }
}
},
selectGroup: {
action: 'selectGroup',
description: 'Emitted when a group is selected.',
table: { category: 'Actions' }
},
removeGroup: {
action: 'removeGroup',
description: 'Emitted when a group is removed.',
table: { category: 'Actions' }
},
changedGroups: {
action: 'changedGroups',
description: 'Emitted when a group selection changes.',
table: { category: 'Actions' }
},
warning: {
action: 'warning',
description: 'Emitted when an warning occurs.',
table: { category: 'Actions' }
}
},
args: {
appName: 'app',
title: 'Groups',
mode: 'single',
preSelectGroups: [],
validate: false,
readOnly: false,
required: false,
roles: []
}
};
export default meta;
type Story = StoryObj<GroupCloudComponent>;
export const DefaultGroupCloud: Story = {
render: (args) => ({
props: args
})
};
export const ValidPreselectedGroups: Story = {
render: (args) => ({
props: args
}),
args: {
validate: true,
mode: 'multiple',
preSelectGroups: mockFoodGroups
}
};
export const MandatoryPreselectedGroups: Story = {
render: (args) => ({
props: args
}),
args: {
validate: true,
mode: 'multiple',
preSelectGroups: [mockVegetableAubergine, { ...mockMeatChicken, readonly: true }]
}
};
export const InvalidPreselectedGroups: Story = {
render: (args) => ({
props: args
}),
args: {
validate: true,
mode: 'multiple',
preSelectGroups: [{ id: 'invalid-group', name: 'Invalid Group' }]
}
};
@@ -15,9 +15,23 @@
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Observable, EMPTY, of } from 'rxjs';
import { IdentityGroupModel } from '../models/identity-group.model';
import { IdentityGroupService } from '@alfresco/adf-process-services-cloud';
export const mockVegetableAubergine: IdentityGroupModel = { id: 'aubergine', name: 'Vegetable Aubergine' };
export const mockMeatChicken: IdentityGroupModel = { id: 'chicken', name: 'Meat Chicken' };
export const mockFoodGroups = [mockVegetableAubergine, mockMeatChicken];
@Injectable()
export class IdentityGroupServiceMock extends IdentityGroupService {
search(name: string): Observable<IdentityGroupModel[]> {
if (name.trim() === '') {
return EMPTY;
}
return of(mockFoodGroups.filter((group) => group.name.toUpperCase().includes(name.toUpperCase())));
}
}
@@ -0,0 +1,238 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { applicationConfig, Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { PeopleCloudComponent } from './people-cloud.component';
import { provideStoryProcessServicesCloud } from '../../stories/process-services-cloud-story.providers';
import { IdentityUserService } from '../services/identity-user.service';
import { IdentityUserServiceMock, mockFoodUsers, mockKielbasaSausage, mockShepherdsPie, mockYorkshirePudding } from '../mock/people-cloud.mock';
const meta: Meta<PeopleCloudComponent> = {
component: PeopleCloudComponent,
title: 'Process Services Cloud/People Cloud/People Cloud',
decorators: [
moduleMetadata({
imports: [PeopleCloudComponent]
}),
applicationConfig({
providers: [{ provide: IdentityUserService, useClass: IdentityUserServiceMock }, ...provideStoryProcessServicesCloud()]
})
],
argTypes: {
appName: {
control: 'text',
description: 'Name of the application. If specified, this shows the users who have access to the app.',
table: {
type: { summary: 'string' }
}
},
mode: {
control: 'radio',
options: ['single', 'multiple'],
description: 'User selection mode.',
table: {
type: { summary: 'ComponentSelectionMode' },
defaultValue: { summary: 'single' }
}
},
roles: {
control: 'object',
description: 'Role names of the users to be listed.',
table: {
type: { summary: 'string[]' },
defaultValue: { summary: '[]' }
}
},
validate: {
control: 'boolean',
description:
'This flag enables the validation on the preSelectUsers passed as input.\n\n' +
'In case the flag is true the components call the identity service to verify the validity of the information passed as input.\n\n' +
'Otherwise, no check will be done.',
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' }
}
},
readOnly: {
control: 'boolean',
description: 'Show the info in readonly mode.',
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' }
}
},
required: {
control: 'boolean',
description: 'Mark this field as required.',
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' }
}
},
preSelectUsers: {
control: 'object',
description:
'Array of users to be pre-selected. All users in the array are pre-selected in multi selection mode, but only the first user is pre-selected in single selection mode. Mandatory properties are: id, email, username',
table: {
type: { summary: 'IdentityUserModel[]' },
defaultValue: { summary: '[]' }
}
},
excludedUsers: {
control: 'object',
description: 'Array of users to be excluded. Mandatory properties are: id, email, username',
table: {
type: { summary: 'IdentityUserModel[]' },
defaultValue: { summary: '[]' }
}
},
groupsRestriction: {
control: 'object',
description: 'Array of groups to restrict user searches. Mandatory property is group name',
table: {
type: { summary: 'string[]' },
defaultValue: { summary: '[]' }
}
},
userChipsCtrl: {
control: 'object',
description: 'FormControl to list of users.',
table: {
type: { summary: 'FormControl' },
defaultValue: { summary: "new FormControl({ value: '', disabled: false })" },
category: 'Form Controls'
}
},
searchUserCtrl: {
control: 'object',
description: 'FormControl to search the user.',
table: {
type: { summary: 'FormControl' },
defaultValue: { summary: "new FormControl({ value: '', disabled: false })" },
category: 'Form Controls'
}
},
title: {
control: 'text',
description: 'Title of the field.',
table: {
type: { summary: 'string' }
}
},
selectUser: {
action: 'selectUser',
description: 'Emitted when a user is selected.',
table: { category: 'Actions' }
},
removeUser: {
action: 'removeUser',
description: 'Emitted when a selected user is removed in multi selection mode.',
table: { category: 'Actions' }
},
changedUsers: {
action: 'changedUsers',
description: 'Emitted when a user selection changes.',
table: { category: 'Actions' }
},
warning: {
action: 'warning',
description: 'Emitted when an warning occurs.',
table: { category: 'Actions' }
}
},
args: {
appName: 'app',
mode: 'single',
roles: [],
validate: false,
readOnly: false,
required: false,
preSelectUsers: [],
excludedUsers: [],
groupsRestriction: []
}
};
export default meta;
type Story = StoryObj<PeopleCloudComponent>;
export const DefaultPeopleCloud: Story = {
render: (args) => ({
props: args
})
};
export const ValidPreselectedUsers: Story = {
render: (args) => ({
props: args
}),
args: {
validate: true,
mode: 'multiple',
preSelectUsers: mockFoodUsers
}
};
export const MandatoryPreselectedUsers: Story = {
render: (args) => ({
props: args
}),
args: {
validate: true,
mode: 'multiple',
preSelectUsers: [{ ...mockKielbasaSausage, readonly: true }, mockShepherdsPie]
}
};
export const InvalidPreselectedUsers: Story = {
render: (args) => ({
props: args
}),
args: {
validate: true,
mode: 'multiple',
preSelectUsers: [{ id: 'invalid-user', username: 'Invalid User', firstName: 'Invalid', lastName: 'User', email: 'invalid@xyz.com' }]
}
};
export const ExcludedUsers: Story = {
render: (args) => ({
props: args
}),
args: {
excludedUsers: [mockKielbasaSausage, mockYorkshirePudding]
}
};
export const NoUsers: Story = {
render: (args) => ({
props: args
}),
args: {
excludedUsers: mockFoodUsers
}
};
export const InvalidOrEmptyAppName: Story = {
render: (args) => ({
props: args
}),
args: {
appName: null
}
};
@@ -0,0 +1,67 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Observable, of, Subject, throwError } from 'rxjs';
import { ProcessInstanceCloud } from '../start-process/models/process-instance-cloud.model';
import { ProcessDefinitionCloud } from '../../models/process-definition-cloud.model';
import { ApplicationVersionModel } from '../../models/application-version.model';
import { processInstancePlaceholdersCloudMock, processInstanceDetailsCloudMock } from './process-instance-details-cloud.mock';
import { fakeProcessDefinitions } from '../start-process/mock/start-process.component.mock';
import { mockAppVersions } from '../process-filters/mock/process-filters-cloud.mock';
import { ProcessCloudService } from '../services/process-cloud.service';
@Injectable()
export class ProcessCloudServiceMock extends ProcessCloudService {
dataChangesDetected = new Subject<ProcessInstanceCloud>();
getProcessInstanceById(appName: string, processInstanceId: string): Observable<ProcessInstanceCloud> {
if (appName === 'app-placeholders' && processInstanceId) {
return of(processInstancePlaceholdersCloudMock);
}
if (appName && processInstanceId) {
return of(processInstanceDetailsCloudMock);
} else {
return throwError(() => 'AppName/ProcessInstanceId not configured');
}
}
getProcessDefinitions(appName: string): Observable<ProcessDefinitionCloud[]> {
if (appName || appName === '') {
return of(fakeProcessDefinitions);
} else {
return throwError(() => 'AppName not configured');
}
}
getApplicationVersions(appName: string): Observable<ApplicationVersionModel[]> {
if (appName) {
return of(mockAppVersions);
} else {
return throwError(() => 'AppName not configured');
}
}
cancelProcess(appName: string, processInstanceId: string): Observable<ProcessInstanceCloud> {
if (appName && processInstanceId) {
return of();
} else {
return throwError(() => 'App name and process id not configured');
}
}
}
@@ -28,3 +28,15 @@ export const processInstanceDetailsCloudMock: ProcessInstanceCloud = {
startDate: new Date(1552152187080),
status: 'RUNNING'
};
export const processInstancePlaceholdersCloudMock: ProcessInstanceCloud = {
appName: 'app-placeholders',
businessKey: '',
id: '00fcc4ab-4290-11e9-b133-0a586460016a',
initiator: 'devopsuser',
lastModified: new Date(2022, 1, 1, 1, 30, 40),
name: '',
parentId: '',
startDate: new Date(1552152187080),
status: 'RUNNING'
};
@@ -0,0 +1,96 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { applicationConfig, Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { provideStoryProcessServicesCloud } from '../../../stories/process-services-cloud-story.providers';
import { ProcessHeaderCloudComponent } from './process-header-cloud.component';
import { ProcessCloudServiceMock } from '../../mock/process-cloud.service.mock';
import { ProcessCloudService } from '../../services/process-cloud.service';
const meta: Meta<ProcessHeaderCloudComponent> = {
component: ProcessHeaderCloudComponent,
title: 'Process Services Cloud/Process Cloud/Process Header Cloud/Process Header Cloud',
decorators: [
moduleMetadata({
imports: [ProcessHeaderCloudComponent]
}),
applicationConfig({
providers: [{ provide: ProcessCloudService, useClass: ProcessCloudServiceMock }, ...provideStoryProcessServicesCloud()]
})
],
argTypes: {
appName: {
control: 'text',
description: '(Required) The name of the application.',
defaultValue: '',
table: {
type: { summary: 'string' },
defaultValue: { summary: '' }
}
},
processInstanceId: {
control: 'text',
description: '(Required) The id of the process instance.',
table: {
type: { summary: 'string' }
}
}
}
};
export default meta;
type Story = StoryObj<ProcessHeaderCloudComponent>;
export const DefaultProcessHeaderCloud: Story = {
render: (args) => ({
props: args
}),
args: {
appName: 'app',
processInstanceId: 'mock-process-id'
}
};
export const NoParentAndBusinessAndName: Story = {
render: (args) => ({
props: args
}),
args: {
appName: 'app-placeholders',
processInstanceId: 'mock-process-id'
}
};
export const InvalidOrMissingAppName: Story = {
render: (args) => ({
props: args
}),
args: {
appName: undefined,
processInstanceId: 'mock-process-id'
}
};
export const InvalidOrMissingProcessInstanceID: Story = {
render: (args) => ({
props: args
}),
args: {
appName: 'app',
processInstanceId: undefined
}
};
@@ -0,0 +1,48 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Provider, EnvironmentProviders } from '@angular/core';
import { provideCoreAuth, provideAppConfig, provideI18N } from '@alfresco/adf-core';
import { provideCloudFormRenderer, provideCloudPreferences } from '../providers';
import { TASK_LIST_CLOUD_TOKEN } from '../services/cloud-token.service';
import { TaskListCloudService } from '../task/task-list/services/task-list-cloud.service';
import { provideAnimations } from '@angular/platform-browser/animations';
import { provideRouter, withHashLocation } from '@angular/router';
/**
* Provides the providers for the process services cloud story.
*
* @returns An array of providers for the process services cloud story.
*/
export function provideStoryProcessServicesCloud(): (Provider | EnvironmentProviders)[] {
return [
provideI18N({
assets: [
['adf-core', 'assets/adf-core'],
['adf-process-services', 'assets/adf-process-services'],
['adf-process-services-cloud', 'assets/adf-process-services-cloud']
]
}),
provideAppConfig(),
provideCoreAuth(),
provideCloudPreferences(),
provideCloudFormRenderer(),
provideAnimations(),
{ provide: TASK_LIST_CLOUD_TOKEN, useClass: TaskListCloudService },
provideRouter([], withHashLocation())
];
}
@@ -0,0 +1,70 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Observable, of, Subject } from 'rxjs';
import { TaskDetailsCloudModel, TASK_ASSIGNED_STATE, TASK_CREATED_STATE } from '../models/task-details-cloud.model';
import { taskDetailsContainer } from '../task-header/mocks/task-details-cloud.mock';
import { TaskCloudService } from '../services/task-cloud.service';
import { AdfHttpClient } from '@alfresco/adf-core/api';
@Injectable()
export class TaskCloudServiceMock extends TaskCloudService {
currentUserMock = 'AssignedTaskUser';
dataChangesDetected$ = new Subject();
constructor(adfHttpClient: AdfHttpClient) {
super(adfHttpClient);
}
getTaskById(_appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
return of(taskDetailsContainer[taskId]);
}
getCandidateUsers(_appName: string, taskId: string): Observable<string[]> {
if (taskId === 'mock-no-candidate-users') {
return of([]);
}
return of(['user1', 'user2']);
}
getCandidateGroups(_appName: string, taskId: string): Observable<string[]> {
if (taskId === 'mock-no-candidate-groups') {
return of([]);
}
return of(['group1', 'group2']);
}
updateTask(_appName: string, taskId: string): Observable<TaskDetailsCloudModel> {
return of(taskDetailsContainer[taskId]);
}
canClaimTask(taskDetails: TaskDetailsCloudModel): boolean {
return taskDetails?.status === TASK_CREATED_STATE;
}
protected isAssignedToMe(assignee: string): boolean {
return assignee === this.currentUserMock;
}
canUnclaimTask(taskDetails: TaskDetailsCloudModel): boolean {
const currentUser = this.currentUserMock;
return taskDetails?.status === TASK_ASSIGNED_STATE && taskDetails.assignee === currentUser;
}
}
@@ -0,0 +1,220 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { applicationConfig, Meta, StoryObj, moduleMetadata } from '@storybook/angular';
import { FormCloudService } from '../../../../form/public-api';
import { TaskCloudService } from '../../../services/task-cloud.service';
import { TaskFormCloudComponent } from './task-form-cloud.component';
import { TaskCloudServiceMock } from '../../../mock/task-cloud.service.mock';
import { FormCloudServiceMock } from '../../../../form/mocks/form-cloud.service.mock';
import { provideStoryProcessServicesCloud } from '../../../../stories/process-services-cloud-story.providers';
import { taskWithFormDetailsMock } from '../../../task-header/mocks/task-details-cloud.mock';
const meta: Meta<TaskFormCloudComponent> = {
component: TaskFormCloudComponent,
title: 'Process Services Cloud/Task Cloud/Task Form/Task Form Cloud',
decorators: [
moduleMetadata({
imports: [TaskFormCloudComponent],
providers: [
{ provide: TaskCloudService, useClass: TaskCloudServiceMock },
{ provide: FormCloudService, useClass: FormCloudServiceMock }
]
}),
applicationConfig({
providers: [...provideStoryProcessServicesCloud()]
})
],
argTypes: {
appName: {
control: 'text',
description: 'App id to fetch corresponding form and values.',
defaultValue: '',
table: {
type: { summary: 'string' },
defaultValue: { summary: '' }
}
},
taskId: {
control: 'text',
description: 'Task id to fetch corresponding form and values.',
table: {
type: { summary: 'string' }
}
},
showTitle: {
control: 'boolean',
description: 'Toggle rendering of the form title.',
defaultValue: true,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'true' }
}
},
showRefreshButton: {
control: 'boolean',
description: 'Toggle rendering of the `Refresh` button.',
defaultValue: false,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' }
}
},
showValidationIcon: {
control: 'boolean',
description: 'Toggle rendering of the `Validation` icon.',
defaultValue: true,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'true' }
}
},
showCancelButton: {
control: 'boolean',
description: 'Toggle rendering of the `Cancel` button.',
defaultValue: true,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'true' }
}
},
showCompleteButton: {
control: 'boolean',
description: 'Toggle rendering of the `Complete` button.',
defaultValue: true,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'true' }
}
},
readOnly: {
control: 'boolean',
description: 'Toggle readonly state of the task.',
defaultValue: false,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' }
}
},
taskDetails: {
control: 'object',
description: 'Task details.',
table: {
type: { summary: 'TaskDetailsCloudModel' }
}
},
displayModeConfigurations: {
control: 'object',
description: 'The available display configurations for the form.',
table: {
type: { summary: 'FormCloudDisplayModeConfiguration[]' }
}
},
formSaved: {
action: 'formSaved',
description: 'Emitted when the form is saved.',
table: { category: 'Actions' }
},
formCompleted: {
action: 'formCompleted',
description: 'Emitted when the form is submitted with the `Complete` outcome.',
table: { category: 'Actions' }
},
taskCompleted: {
action: 'taskCompleted',
description: 'Emitted when the task is completed.',
table: { category: 'Actions' }
},
taskClaimed: {
action: 'taskClaimed',
description: 'Emitted when the task is claimed.',
table: { category: 'Actions' }
},
taskUnclaimed: {
action: 'taskUnclaimed',
description: 'Emitted when the task is unclaimed.',
table: { category: 'Actions' }
},
candidateUsers: {
control: 'object',
description: 'Candidate users.',
table: {
type: { summary: 'string[]' },
defaultValue: { summary: '[]' }
}
},
candidateGroups: {
control: 'object',
description: 'Candidate groups.',
table: {
type: { summary: 'string[]' },
defaultValue: { summary: '[]' }
}
},
cancelClick: {
action: 'cancelClick',
description: 'Emitted when the cancel button is clicked.',
table: { category: 'Actions' }
},
formContentClicked: {
action: 'formContentClicked',
description: 'Emitted when form content is clicked.',
table: { category: 'Actions' }
},
error: {
action: 'error',
description: 'Emitted when any error occurs.',
table: { category: 'Actions' }
}
}
};
export default meta;
type Story = StoryObj<TaskFormCloudComponent>;
export const DefaultTaskFormCloud: Story = {
render: (args) => ({
props: args
}),
args: {
appName: 'app',
taskId: 'mock-task-with-form',
taskDetails: { ...taskWithFormDetailsMock }
}
};
export const InvalidOrMissingApp: Story = {
render: (args) => ({
props: args
}),
args: {
...DefaultTaskFormCloud.args,
appName: undefined,
taskDetails: { ...taskWithFormDetailsMock }
}
};
export const InvalidOrMissingTaskId: Story = {
render: (args) => ({
props: args
}),
args: {
...DefaultTaskFormCloud.args,
taskId: undefined,
taskDetails: { ...taskWithFormDetailsMock }
}
};
@@ -0,0 +1,170 @@
/*!
* @license
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Meta, StoryObj, applicationConfig, moduleMetadata } from '@storybook/angular';
import { TaskHeaderCloudComponent } from './task-header-cloud.component';
import { TaskCloudService } from '../../services/task-cloud.service';
import { TaskCloudServiceMock } from '../../mock/task-cloud.service.mock';
import { provideStoryProcessServicesCloud } from '../../../stories/process-services-cloud-story.providers';
const meta: Meta<TaskHeaderCloudComponent> = {
component: TaskHeaderCloudComponent,
title: 'Process Services Cloud/Task Cloud/Task Header Cloud/Task Header Cloud',
decorators: [
applicationConfig({
providers: [...provideStoryProcessServicesCloud(), { provide: TaskCloudService, useClass: TaskCloudServiceMock }]
}),
moduleMetadata({
imports: [TaskHeaderCloudComponent]
})
],
argTypes: {
appName: {
control: 'text',
description: '(Required) The name of the application.',
defaultValue: '',
table: {
type: { summary: 'string' },
defaultValue: { summary: '' }
}
},
taskId: {
control: 'text',
description: '(Required) The id of the task.',
table: {
type: { summary: 'string' }
}
},
showTitle: {
control: 'boolean',
description: 'Show/Hide the task title.',
defaultValue: true,
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'true' }
}
},
claim: {
action: 'claim',
description: 'Emitted when the task is claimed.',
table: { category: 'Actions' }
},
unclaim: {
action: 'unclaim',
description: 'Emitted when the task is unclaimed (ie, requeued).',
table: { category: 'Actions' }
},
error: {
action: 'error',
description: 'Emitted when the given task has errors.',
table: { category: 'Actions' }
}
}
};
export default meta;
type Story = StoryObj<TaskHeaderCloudComponent>;
export const AssignedAndEditable: Story = {
render: (args) => ({
props: args
}),
args: {
appName: 'app',
taskId: 'mock-assigned-task'
}
};
export const CompletedAndReadonly: Story = {
render: (args) => ({
props: args
}),
args: {
...AssignedAndEditable.args,
taskId: 'mock-completed-task'
}
};
export const Suspended: Story = {
render: (args) => ({
props: args
}),
args: {
...AssignedAndEditable.args,
taskId: 'mock-suspended-task'
}
};
export const WithParentId: Story = {
render: (args) => ({
props: args
}),
args: {
...AssignedAndEditable.args,
taskId: 'mock-parent-task-id'
}
};
export const WithoutAssignee: Story = {
render: (args) => ({
props: args
}),
args: {
...AssignedAndEditable.args,
taskId: 'mock-created-task'
}
};
export const NotClaimableByUser: Story = {
render: (args) => ({
props: args
}),
args: {
...AssignedAndEditable.args,
taskId: 'mock-no-candidate-users'
}
};
export const TaskNotClaimableByGroupUser: Story = {
render: (args) => ({
props: args
}),
args: {
...AssignedAndEditable.args,
taskId: 'mock-no-candidate-groups'
}
};
export const InvalidForMissingApp: Story = {
render: (args) => ({
props: args
}),
args: {
...AssignedAndEditable.args,
appName: undefined
}
};
export const InvalidForMissingTaskId: Story = {
render: (args) => ({
props: args
}),
args: {
...AssignedAndEditable.args,
taskId: undefined
}
};
@@ -130,8 +130,8 @@ export class TaskHeaderCloudComponent implements OnInit, OnChanges {
.subscribe(
([taskDetails, candidateUsers, candidateGroups]) => {
this.taskDetails = taskDetails;
this.candidateGroups = candidateGroups.map((user) => ({ icon: 'group', value: user } as CardViewArrayItem));
this.candidateUsers = candidateUsers.map((group) => ({ icon: 'person', value: group } as CardViewArrayItem));
this.candidateGroups = candidateGroups.map((user) => ({ icon: 'group', value: user }) as CardViewArrayItem);
this.candidateUsers = candidateUsers.map((group) => ({ icon: 'person', value: group }) as CardViewArrayItem);
this.processInstanceId = taskDetails.processInstanceId;
if (this.taskDetails.parentTaskId) {
this.loadParentName(`${this.taskDetails.parentTaskId}`);
@@ -180,3 +180,80 @@ export const suspendedTaskDetailsCloudMock: TaskDetailsCloudModel = {
lastModifiedTo: null,
lastModifiedFrom: null
};
export const noCandidateUsersTaskDetailsCloudMock: TaskDetailsCloudModel = {
appName: 'mock-app-name',
appVersion: 1,
id: 'mock-task-id',
assignee: '',
name: 'This is a new task',
description: 'This is the description ',
createdDate: new Date(1545048055900),
dueDate: new Date(1545091200000),
claimedDate: null,
priority: 5,
category: null,
processDefinitionId: null,
processInstanceId: null,
status: 'CREATED',
owner: 'ownerUser',
candidateUsers: null,
parentTaskId: null,
formKey: null,
lastModified: new Date(1545048055900),
lastModifiedTo: null,
lastModifiedFrom: null,
standalone: false
};
export const noCandidateGroupsTaskDetailsCloudMock: TaskDetailsCloudModel = {
appName: 'mock-app-name',
appVersion: 1,
id: 'mock-task-id',
assignee: '',
name: 'This is a new task',
description: 'This is the description ',
createdDate: new Date(1545048055900),
dueDate: new Date(1545091200000),
claimedDate: null,
priority: 5,
category: null,
processDefinitionId: null,
processInstanceId: null,
status: 'CREATED',
owner: 'ownerUser',
candidateGroups: null,
parentTaskId: null,
formKey: null,
lastModified: new Date(1545048055900),
lastModifiedTo: null,
lastModifiedFrom: null,
standalone: false
};
export const taskWithFormDetailsMock: TaskDetailsCloudModel = {
appName: 'mock-app-name',
assignee: 'AssignedTaskUser',
completedDate: null,
createdDate: new Date(1555419255340),
dueDate: new Date(1558419255340),
description: null,
formKey: '4',
priority: 1,
parentTaskId: 'bd6b1741-6046-11e9-80f0-0a586460040d',
id: 'bd6b1741-6046-11e9-80f0-0a586460040d',
name: 'Task1',
owner: 'fakeAdmin',
status: 'ASSIGNED'
};
export const taskDetailsContainer = {
'mock-assigned-task': assignedTaskDetailsCloudMock,
'mock-completed-task': completedTaskDetailsCloudMock,
'mock-created-task': createdStateTaskDetailsCloudMock,
'mock-parent-task-id': taskDetailsWithParentTaskIdMock,
'mock-suspended-task': suspendedTaskDetailsCloudMock,
'mock-no-candidate-users': noCandidateUsersTaskDetailsCloudMock,
'mock-no-candidate-groups': noCandidateGroupsTaskDetailsCloudMock,
'mock-task-with-form': taskWithFormDetailsMock
};