From e830d6209aa355105e81a9a955859932a85408ac Mon Sep 17 00:00:00 2001 From: DominikIwanek Date: Thu, 18 Sep 2025 08:50:12 +0200 Subject: [PATCH] [ACS-10117] Deprecate ADF Storybook and custom Docker builds --- .../core/components/login-dialog.component.md | 91 ++++++++ ...orm-definition-selector-cloud.component.md | 35 +++ .../login-dialog-component-data.interface.ts | 24 ++ .../login-dialog/login-dialog.component.html | 23 ++ .../login-dialog/login-dialog.component.scss | 4 + .../login-dialog/login-dialog.component.ts | 59 +++++ lib/core/src/lib/login/login.module.ts | 9 +- .../app-details-cloud.component.html | 23 ++ .../app-details-cloud.component.scss | 167 +++++++++++++ .../app-details-cloud.component.spec.ts | 72 ++++++ .../app-details-cloud.component.ts | 66 ++++++ .../app-list-cloud.component.html | 46 ++++ .../app-list-cloud.component.scss | 41 ++++ .../app-list-cloud.component.spec.ts | 220 ++++++++++++++++++ .../app-list-cloud.component.ts | 133 +++++++++++ .../app/models/application-instance.model.ts | 3 + ...m-definition-selector-cloud.component.html | 7 + ...m-definition-selector-cloud.component.scss | 5 + ...efinition-selector-cloud.component.spec.ts | 79 +++++++ ...orm-definition-selector-cloud.component.ts | 53 +++++ .../form/mocks/form-representation.mock.ts | 45 ++++ ...-definition-selector-cloud.service.spec.ts | 55 +++++ .../form-definition-selector-cloud.service.ts | 53 +++++ .../src/lib/process-services-cloud.module.ts | 8 +- 24 files changed, 1319 insertions(+), 2 deletions(-) create mode 100644 docs/core/components/login-dialog.component.md create mode 100644 docs/process-services-cloud/components/form-definition-selector-cloud.component.md create mode 100644 lib/core/src/lib/login/components/login-dialog/login-dialog-component-data.interface.ts create mode 100644 lib/core/src/lib/login/components/login-dialog/login-dialog.component.html create mode 100644 lib/core/src/lib/login/components/login-dialog/login-dialog.component.scss create mode 100644 lib/core/src/lib/login/components/login-dialog/login-dialog.component.ts create mode 100644 lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.html create mode 100644 lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.scss create mode 100644 lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.spec.ts create mode 100644 lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.ts create mode 100644 lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.html create mode 100644 lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.scss create mode 100644 lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.spec.ts create mode 100644 lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.ts create mode 100644 lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.html create mode 100644 lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.scss create mode 100644 lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.spec.ts create mode 100644 lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.ts create mode 100644 lib/process-services-cloud/src/lib/form/mocks/form-representation.mock.ts create mode 100644 lib/process-services-cloud/src/lib/form/services/form-definition-selector-cloud.service.spec.ts create mode 100644 lib/process-services-cloud/src/lib/form/services/form-definition-selector-cloud.service.ts diff --git a/docs/core/components/login-dialog.component.md b/docs/core/components/login-dialog.component.md new file mode 100644 index 0000000000..b7b416eddf --- /dev/null +++ b/docs/core/components/login-dialog.component.md @@ -0,0 +1,91 @@ +--- +Title: Login Dialog component +Added: v2.6.0 +Status: Active +Last reviewed: 2018-10-02 +--- + +# [Login Dialog component](../../../lib/core/src/lib/login/components/login-dialog.component.ts "Defined in login-dialog.component.ts") + +This component is deprecated and will removed because it's unused. + +Allows a user to perform a login via a dialog. + +## Details + +The [Login Dialog component](login-dialog.component.md) allows you to perform a login via a dialog. + +### Showing the dialog + +Unlike most components, the [Login Dialog Component](login-dialog.component.md) is typically shown in a dialog box +rather than the main page and you are responsible for opening the dialog yourself. You can use the +[Angular Material Dialog](https://material.angular.io/components/dialog/overview) for this, +as shown in the usage example. ADF provides the [`LoginDialogComponentData`](../../../lib/core/src/lib/login/components/login-dialog-component-data.interface.ts) interface +to work with the Dialog's +[data option](https://material.angular.io/components/dialog/overview#sharing-data-with-the-dialog-component-): + +```ts +export interface LoginDialogComponentData { + title: string; + actionName?: string; + logged: Subject; +} +``` + +The properties are described in the table below: + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| title | `string` | "" | Dialog title | +| actionName | `string` | "" | Text to appear on the dialog's main action button ("Login", "Access", etc) | +| logged | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | | Event emitted when the login succeeds. | + +If you don't want to manage the dialog yourself then it is easier to use the +[Login Dialog Panel component](login-dialog-panel.component.md), or the +methods of the [Login Dialog service](../services/login-dialog.service.md), which create +the dialog for you. + +### Usage example + +```ts +import { MatDialog } from '@angular/material/dialog'; +import { LoginDialogComponentData, LoginDialogComponent} from '@adf/core' +import { Subject } from 'rxjs/Subject'; + ... + +constructor(dialog: MatDialog ... ) {} + +openLoginDialog() { + data: LoginDialogComponentData = { + title: "Perform a Login", + actionName: "Access", + logged: new Subject() + }; + + this.dialog.open( + LoginDialogComponent, + { + data, panelClass: 'adf-login-dialog', + width: '630px' + } + ); + + data.logged.subscribe(() => { + // Action after being logged in... + }, + (error)=>{ + //your error handling + }, + ()=>{ + //action called when an action or cancel is clicked on the dialog + this.dialog.closeAll(); + }); +} +``` + +All the results will be streamed to the logged [subject](http://reactivex.io/rxjs/manual/overview.html#subject) present in the [`LoginDialogComponentData`](../../../lib/core/src/lib/login/components/login-dialog-component-data.interface.ts) object passed to the dialog. +When the dialog action is selected by clicking, the `data.logged` stream will be completed. + +## See also + +- [Login component](login.component.md) diff --git a/docs/process-services-cloud/components/form-definition-selector-cloud.component.md b/docs/process-services-cloud/components/form-definition-selector-cloud.component.md new file mode 100644 index 0000000000..1cfb5a3405 --- /dev/null +++ b/docs/process-services-cloud/components/form-definition-selector-cloud.component.md @@ -0,0 +1,35 @@ +--- +Title: Form Definition Selector Cloud +Added: v3.3.0 +Status: Active +--- + +# [Form Definition Selector Cloud](../../../lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.ts "Defined in form-definition-selector-cloud.component.ts") + +Allows one form to be selected from a dropdown list. For forms to be displayed in this component they will need to be compatible with standAlone tasks. + +This component will be removed because it's unused. + + +## Basic Usage + +```html + + +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| appName | `string` | "" | Name of the application. If specified, this shows the users who have access to the app. | + +### Events + +| Name | Type | Description | +| ---- | ---- | ----------- | +| selectForm | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when a form is selected. | diff --git a/lib/core/src/lib/login/components/login-dialog/login-dialog-component-data.interface.ts b/lib/core/src/lib/login/components/login-dialog/login-dialog-component-data.interface.ts new file mode 100644 index 0000000000..d058ff6792 --- /dev/null +++ b/lib/core/src/lib/login/components/login-dialog/login-dialog-component-data.interface.ts @@ -0,0 +1,24 @@ +/*! + * @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 { Subject } from 'rxjs'; + +export interface LoginDialogComponentData { + title: string; + actionName?: string; + logged: Subject; +} diff --git a/lib/core/src/lib/login/components/login-dialog/login-dialog.component.html b/lib/core/src/lib/login/components/login-dialog/login-dialog.component.html new file mode 100644 index 0000000000..22b918bfac --- /dev/null +++ b/lib/core/src/lib/login/components/login-dialog/login-dialog.component.html @@ -0,0 +1,23 @@ +
+ {{ data?.title }} +
+ + + + diff --git a/lib/core/src/lib/login/components/login-dialog/login-dialog.component.scss b/lib/core/src/lib/login/components/login-dialog/login-dialog.component.scss new file mode 100644 index 0000000000..7956689d96 --- /dev/null +++ b/lib/core/src/lib/login/components/login-dialog/login-dialog.component.scss @@ -0,0 +1,4 @@ +.adf-login-dialog-content adf-login .adf-login-content .adf-login-card-wide { + padding: 0; + box-shadow: none; +} diff --git a/lib/core/src/lib/login/components/login-dialog/login-dialog.component.ts b/lib/core/src/lib/login/components/login-dialog/login-dialog.component.ts new file mode 100644 index 0000000000..705466c14d --- /dev/null +++ b/lib/core/src/lib/login/components/login-dialog/login-dialog.component.ts @@ -0,0 +1,59 @@ +/*! + * @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 { Component, Inject, ViewChild, ViewEncapsulation } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { TranslatePipe } from '@ngx-translate/core'; +import { LoginDialogPanelComponent } from '../login-dialog-panel/login-dialog-panel.component'; +import { LoginDialogComponentData } from './login-dialog-component-data.interface'; + +/** @deprecated this component will be removed because it's unused */ +@Component({ + selector: 'adf-login-dialog', + templateUrl: './login-dialog.component.html', + styleUrls: ['./login-dialog.component.scss'], + imports: [MatDialogModule, LoginDialogPanelComponent, TranslatePipe, MatButtonModule], + encapsulation: ViewEncapsulation.None +}) +export class LoginDialogComponent { + @ViewChild('adfLoginPanel', { static: true }) + loginPanel: LoginDialogPanelComponent; + + buttonActionName = ''; + + constructor(@Inject(MAT_DIALOG_DATA) public data: LoginDialogComponentData) { + this.buttonActionName = data.actionName ? `LOGIN.DIALOG.${data.actionName.toUpperCase()}` : 'LOGIN.DIALOG.CHOOSE'; + } + + close() { + this.data.logged.complete(); + } + + submitForm(): void { + this.loginPanel.submitForm(); + } + + onLoginSuccess(event: any) { + this.data.logged.next(event); + this.close(); + } + + isFormValid() { + return this.loginPanel ? this.loginPanel.isValid() : false; + } +} diff --git a/lib/core/src/lib/login/login.module.ts b/lib/core/src/lib/login/login.module.ts index c07e8e9177..251a1892f4 100644 --- a/lib/core/src/lib/login/login.module.ts +++ b/lib/core/src/lib/login/login.module.ts @@ -17,12 +17,19 @@ import { NgModule } from '@angular/core'; import { LoginDialogPanelComponent } from './components/login-dialog-panel/login-dialog-panel.component'; +import { LoginDialogComponent } from './components/login-dialog/login-dialog.component'; import { LoginComponent } from './components/login/login.component'; import { LoginFooterDirective } from './directives/login-footer.directive'; import { LoginHeaderDirective } from './directives/login-header.directive'; -export const LOGIN_DIRECTIVES = [LoginComponent, LoginFooterDirective, LoginHeaderDirective, LoginDialogPanelComponent] as const; +export const LOGIN_DIRECTIVES = [ + LoginComponent, + LoginFooterDirective, + LoginHeaderDirective, + LoginDialogComponent, + LoginDialogPanelComponent +] as const; /** @deprecated use `...LOGIN_DIRECTIVES` or import the standalone directives directly */ @NgModule({ diff --git a/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.html b/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.html new file mode 100644 index 0000000000..268506c2f1 --- /dev/null +++ b/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.html @@ -0,0 +1,23 @@ +
+
+ + +
+

{{applicationInstance.name}}

+
+ +
{{applicationInstance.description}}
+
+
+
+
diff --git a/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.scss b/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.scss new file mode 100644 index 0000000000..6547eaaed5 --- /dev/null +++ b/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.scss @@ -0,0 +1,167 @@ +/* stylelint-disable scss/no-global-function-names */ +@mixin adf-line-clamp($line-height: 1.25, $lines: 3) { + position: relative; + line-height: $line-height; + padding: 0; + overflow: hidden; + text-overflow: ellipsis; + + /* stylelint-disable */ + @supports (-webkit-line-clamp: 1) { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: $lines; + height: calc(0.99em * #{$line-height} * #{$lines}); + } + + @supports not (-webkit-line-clamp: 1) { + white-space: nowrap; + width: 100%; + } + /* stylelint-enable */ +} + +$tile-themes: ( + theme-1: ( + bg: #269abc, + color: #168aac + ), + theme-2: ( + bg: #7da9b0, + color: #6d99a0 + ), + theme-3: ( + bg: #7689ab, + color: #66799b + ), + theme-4: ( + bg: #c74e3e, + color: #b73e2e + ), + theme-5: ( + bg: #fab96c, + color: #eaa95c + ), + theme-6: ( + bg: #759d4c, + color: #658d3c + ), + theme-7: ( + bg: #b1b489, + color: #a1a479 + ), + theme-8: ( + bg: #a17299, + color: #916289 + ), + theme-9: ( + bg: #696c67, + color: #595c57 + ), + theme-10: ( + bg: #cabb33, + color: #baab23 + ) +); + +adf-cloud-app-details { + .adf-app-listgrid { + padding: 8px; + display: block; + + .adf-app-listgrid-item { + outline: none; + padding: 8px; + box-sizing: border-box; + flex: unset; + max-width: unset; + + &-card { + @for $i from 1 through 10 { + &.theme-#{$i} { + $tile-theme: map-get($tile-themes, theme-#{$i}); + + background-color: map-get($tile-theme, bg); + + .adf-app-listgrid-item-card-logo-icon { + color: map-get($tile-theme, color); + } + } + } + + outline: none; + transition: + transform 280ms cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); + min-height: 200px; + flex-direction: column; + box-sizing: border-box; + display: flex; + padding: 0; + max-width: unset; + + &:hover { + box-shadow: + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12), + 0 5px 5px -3px rgba(0, 0, 0, 0.2); + cursor: pointer; + transform: scale(1.015); + } + + &-logo { + position: absolute; + right: 20px; + top: 20px; + padding: 16px; + z-index: 9; + + .adf-app-listgrid-item-card-logo-icon { + font-size: 70px; + width: 1em; + height: 1em; + } + } + + &-title:has(.adf-app-listgrid-item-card-title-text) { + padding: 16px; + margin-bottom: 0; + z-index: 9999; + + h1 { + color: white; + width: 80%; + font-size: var(--theme-headline-font-size); + margin: 0; + line-height: normal; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + } + + &-subtitle:has(.adf-line-clamp) { + color: white; + z-index: 9999; + padding: 16px; + flex: 1 0 auto; + + .adf-line-clamp { + @include adf-line-clamp(1.25, 3); + } + } + + &-actions { + padding: 0 16px 16px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + min-height: 48px; + box-sizing: border-box; + + &-icon { + color: #e9f1f3; + } + } + } + } + } +} diff --git a/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.spec.ts new file mode 100644 index 0000000000..e9193787df --- /dev/null +++ b/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.spec.ts @@ -0,0 +1,72 @@ +/*! + * @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 { ComponentFixture, TestBed } from '@angular/core/testing'; +import { fakeApplicationInstance } from '../../mock/app-model.mock'; +import { AppDetailsCloudComponent } from './app-details-cloud.component'; +import { DEFAULT_APP_INSTANCE_THEME } from '../../models/application-instance.model'; +import { NoopTranslateModule } from '@alfresco/adf-core'; + +describe('AppDetailsCloudComponent', () => { + let component: AppDetailsCloudComponent; + let fixture: ComponentFixture; + let host: HTMLElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [NoopTranslateModule, AppDetailsCloudComponent] + }); + fixture = TestBed.createComponent(AppDetailsCloudComponent); + component = fixture.componentInstance; + host = fixture.nativeElement as HTMLElement; + component.applicationInstance = fakeApplicationInstance[0]; + }); + + const getAppCard = () => host.querySelector('.adf-app-listgrid-item-card'); + + it('should display application name', () => { + fixture.detectChanges(); + const appName = host.querySelector('.adf-app-listgrid-item-card-title'); + expect(appName.innerText.trim()).toEqual(fakeApplicationInstance[0].name); + }); + + it('should emit a click event when app selected', () => { + spyOn(component.selectedApp, 'emit'); + fixture.detectChanges(); + const app = getAppCard(); + app.click(); + expect(component.selectedApp.emit).toHaveBeenCalledWith(fakeApplicationInstance[0]); + }); + + it('should render card with default icon and theme when are not provided', () => { + component.applicationInstance = fakeApplicationInstance[2]; + fixture.detectChanges(); + + const card = getAppCard(); + expect(card.classList.contains(DEFAULT_APP_INSTANCE_THEME)); + + const icon = host.querySelector('.adf-app-listgrid-item-card-logo-icon'); + expect(icon).toBeTruthy(); + }); + + it('should render card with a non ApplicationInstanceModel input object', () => { + component.applicationInstance = { name: 'application-new-3', createdAt: '2018-09-21T12:31:39.000Z', status: 'Pending' }; + fixture.detectChanges(); + const app = getAppCard(); + expect(app).toBeTruthy(); + }); +}); diff --git a/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.ts b/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.ts new file mode 100644 index 0000000000..a77bcd1a37 --- /dev/null +++ b/lib/process-services-cloud/src/lib/app/components/app-details-cloud/app-details-cloud.component.ts @@ -0,0 +1,66 @@ +/*! + * @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 { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; +import { ApplicationInstanceModel, DEFAULT_APP_INSTANCE_ICON, DEFAULT_APP_INSTANCE_THEME } from '../../models/application-instance.model'; +import { CommonModule } from '@angular/common'; +import { TranslatePipe } from '@ngx-translate/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatCardModule } from '@angular/material/card'; + +/** @deprecated this component will be removed because it's unused */ +@Component({ + selector: 'adf-cloud-app-details', + imports: [CommonModule, MatIconModule, MatCardModule], + templateUrl: './app-details-cloud.component.html', + styleUrls: ['./app-details-cloud.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class AppDetailsCloudComponent { + @Input({ required: true }) + applicationInstance: ApplicationInstanceModel; + + @Output() + selectedApp = new EventEmitter(); + + /** + * Pass the selected app as next + * + * @param app application model + */ + onSelectApp(app: ApplicationInstanceModel): void { + this.selectedApp.emit(app); + } + + /** + * Get application instance theme + * + * @returns the name of the theme + */ + getTheme(): string { + return this.applicationInstance.theme || DEFAULT_APP_INSTANCE_THEME; + } + + /** + * Get application instance icon + * + * @returns the name of the icon + */ + getIcon(): string { + return this.applicationInstance.icon || DEFAULT_APP_INSTANCE_ICON; + } +} diff --git a/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.html b/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.html new file mode 100644 index 0000000000..ded3c2eefc --- /dev/null +++ b/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.html @@ -0,0 +1,46 @@ + + +
+ + + + + +
+
+ +
+ +
+ + +
+ +
+
+
+
diff --git a/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.scss b/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.scss new file mode 100644 index 0000000000..7d7ffa931a --- /dev/null +++ b/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.scss @@ -0,0 +1,41 @@ +@use '../../../flex' as flex; + +adf-cloud-app-list { + width: 100%; + + .adf-app-list-item { + cursor: pointer; + } + + .adf-app-list-spinner, + .adf-app-list-empty, + .adf-app-list-error { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + height: 85vh; + + .adf-app-list-cloud-spinner { + margin: 0 auto; + } + } + + .adf-app-apps-grid { + flex-flow: row wrap; + display: flex; + + adf-cloud-app-details { + flex: 1 1 100%; + max-width: 33.3333%; + + @include flex.layout-bp(lt-md) { + max-width: 50%; + } + + @include flex.layout-bp(lt-sm) { + max-width: 100%; + } + } + } +} diff --git a/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.spec.ts new file mode 100644 index 0000000000..aa3445f25b --- /dev/null +++ b/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.spec.ts @@ -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 { Component } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { AlfrescoApiService } from '@alfresco/adf-content-services'; +import { of, throwError } from 'rxjs'; +import { fakeApplicationInstance } from '../../mock/app-model.mock'; +import { AppListCloudComponent, LAYOUT_GRID, LAYOUT_LIST } from './app-list-cloud.component'; +import { AppsProcessCloudService } from '../../services/apps-process-cloud.service'; +import { MatIconModule } from '@angular/material/icon'; +import { CustomEmptyContentTemplateDirective, NoopTranslateModule } from '@alfresco/adf-core'; + +describe('AppListCloudComponent', () => { + let component: AppListCloudComponent; + let fixture: ComponentFixture; + let appsProcessCloudService: AppsProcessCloudService; + let getAppsSpy: jasmine.Spy; + let alfrescoApiService: AlfrescoApiService; + + const mock: any = { + oauth2Auth: { + callCustomApi: () => Promise.resolve(fakeApplicationInstance) + }, + isLoggedIn: () => false, + reply: jasmine.createSpy('reply') + }; + + @Component({ + imports: [MatIconModule, CustomEmptyContentTemplateDirective, AppListCloudComponent], + template: ` + + + apps +

No Apps Found

+
+
+ ` + }) + class CustomEmptyAppListCloudTemplateComponent {} + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [NoopTranslateModule, AppListCloudComponent, CustomEmptyAppListCloudTemplateComponent] + }); + fixture = TestBed.createComponent(AppListCloudComponent); + component = fixture.componentInstance; + alfrescoApiService = TestBed.inject(AlfrescoApiService); + appsProcessCloudService = TestBed.inject(AppsProcessCloudService); + + spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock); + getAppsSpy = spyOn(appsProcessCloudService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance)); + }); + + it('should define layoutType with the default value', () => { + component.layoutType = ''; + fixture.detectChanges(); + expect(component.isGrid()).toBe(true); + }); + + it('Should fetch deployed apps', (done) => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + component.apps$.subscribe((response: any[]) => { + expect(response).toBeDefined(); + expect(response.length).toEqual(3); + expect(response[0].name).toEqual('application-new-1'); + expect(response[0].status).toEqual('Deployed'); + expect(response[0].icon).toEqual('favorite_border'); + expect(response[0].theme).toEqual('theme-2'); + expect(response[1].name).toEqual('application-new-2'); + expect(response[1].status).toEqual('Pending'); + expect(response[1].icon).toEqual('favorite_border'); + expect(response[1].theme).toEqual('theme-2'); + done(); + }); + }); + }); + + it('should display default adf-empty-content template when response empty', () => { + getAppsSpy.and.returnValue(of([])); + fixture.detectChanges(); + const defaultEmptyTemplate = fixture.nativeElement.querySelector('.adf-app-list-empty'); + const emptyContent = fixture.debugElement.nativeElement.querySelector('.adf-empty-content'); + const emptyTitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__title'); + const emptySubtitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__subtitle'); + expect(defaultEmptyTemplate).toBeDefined(); + expect(defaultEmptyTemplate).not.toBeNull(); + expect(emptyContent).not.toBeNull(); + expect(emptyTitle.innerText).toBe('ADF_CLOUD_TASK_LIST.APPS.NO_APPS.TITLE'); + expect(emptySubtitle.innerText).toBe('ADF_CLOUD_TASK_LIST.APPS.NO_APPS.SUBTITLE'); + expect(getAppsSpy).toHaveBeenCalled(); + }); + + it('should display default no permissions template when response returns exception', (done) => { + getAppsSpy.and.returnValue(throwError({})); + fixture.detectChanges(); + fixture.whenStable().then(() => { + component.loadingError$.next(true); + fixture.detectChanges(); + const errorTemplate = fixture.nativeElement.querySelector('.adf-app-list-error'); + const errorTitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__title'); + const errorSubtitle = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__subtitle'); + expect(errorTemplate).not.toBeNull(); + expect(errorTitle.innerText).toBe('ADF_CLOUD_TASK_LIST.APPS.ERROR.TITLE'); + expect(errorSubtitle.innerText).toBe('ADF_CLOUD_TASK_LIST.APPS.ERROR.SUBTITLE'); + expect(getAppsSpy).toHaveBeenCalled(); + done(); + }); + }); + + describe('Grid Layout ', () => { + it('should display a grid by default', () => { + fixture.detectChanges(); + expect(component.isGrid()).toBe(true); + expect(component.isList()).toBe(false); + }); + + it('should defined adf-cloud-app-details when layout type is grid', () => { + fixture.detectChanges(); + const adfCloudDetailsElement = fixture.nativeElement.querySelectorAll('adf-cloud-app-details'); + const appName = fixture.nativeElement.querySelector('.adf-app-listgrid-item-card-title'); + expect(adfCloudDetailsElement).toBeDefined(); + expect(adfCloudDetailsElement).not.toBeNull(); + + expect(adfCloudDetailsElement.length).toEqual(3); + expect(component.isGrid()).toBe(true); + expect(component.isList()).toBe(false); + + expect(appName.innerText.trim()).toEqual(fakeApplicationInstance[0].name); + }); + + it('should display a grid when configured to', () => { + component.layoutType = LAYOUT_GRID; + fixture.detectChanges(); + expect(component.isGrid()).toBe(true); + expect(component.isList()).toBe(false); + }); + + it('should throw an exception on init if unknown type configured', () => { + component.layoutType = 'unknown'; + expect(component.ngOnInit).toThrowError(); + }); + }); + + describe('List Layout ', () => { + beforeEach(() => { + component.layoutType = LAYOUT_LIST; + }); + + it('should display a LIST when configured to', () => { + fixture.detectChanges(); + expect(component.isGrid()).toBe(false); + expect(component.isList()).toBe(true); + }); + + it('should display list when layout type is LIST', () => { + fixture.detectChanges(); + const appListElement = fixture.nativeElement.querySelectorAll('.adf-app-list'); + const appListItemElement = fixture.nativeElement.querySelectorAll('.adf-app-list-item'); + const appName = fixture.nativeElement.querySelector('.adf-app-list-item-text'); + expect(appListElement).toBeDefined(); + expect(appListElement).not.toBeNull(); + + expect(appListItemElement.length).toEqual(3); + expect(component.isGrid()).toBe(false); + expect(component.isList()).toBe(true); + + expect(appName.innerText.trim()).toEqual(fakeApplicationInstance[0].name); + }); + + it('should throw an exception on init if unknown type configured', () => { + component.layoutType = 'unknown'; + expect(component.ngOnInit).toThrowError(); + }); + }); + + it('should emit a click event when app selected', () => { + spyOn(component.appClick, 'emit'); + fixture.detectChanges(); + const onAppClick = fixture.nativeElement.querySelector('.adf-app-listgrid-item-card'); + onAppClick.click(); + expect(component.appClick.emit).toHaveBeenCalledWith(fakeApplicationInstance[0]); + }); + + describe('Custom CustomEmptyAppListCloudTemplateComponent', () => { + let customFixture: ComponentFixture; + + beforeEach(() => { + getAppsSpy.and.returnValue(of([])); + customFixture = TestBed.createComponent(CustomEmptyAppListCloudTemplateComponent); + }); + + afterEach(() => { + customFixture.destroy(); + }); + + it('should render the custom empty template', async () => { + customFixture.detectChanges(); + await customFixture.whenStable(); + + const title = customFixture.nativeElement.querySelector('#custom-id'); + expect(title.innerText).toBe('No Apps Found'); + }); + }); +}); diff --git a/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.ts b/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.ts new file mode 100644 index 0000000000..801bf8770c --- /dev/null +++ b/lib/process-services-cloud/src/lib/app/components/app-list-cloud/app-list-cloud.component.ts @@ -0,0 +1,133 @@ +/*! + * @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 { CustomEmptyContentTemplateDirective, EmptyContentComponent } from '@alfresco/adf-core'; +import { AfterContentInit, Component, ContentChild, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { Observable, of, Subject } from 'rxjs'; +import { AppsProcessCloudService } from '../../services/apps-process-cloud.service'; +import { ApplicationInstanceModel } from '../../models/application-instance.model'; +import { catchError } from 'rxjs/operators'; +import { CommonModule } from '@angular/common'; +import { TranslatePipe } from '@ngx-translate/core'; +import { AppDetailsCloudComponent } from '../app-details-cloud/app-details-cloud.component'; +import { MatIconModule } from '@angular/material/icon'; +import { MatLineModule } from '@angular/material/core'; +import { MatListModule } from '@angular/material/list'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; + +export const LAYOUT_LIST: string = 'LIST'; +export const LAYOUT_GRID: string = 'GRID'; +export const DEPLOYED_STATUS: string = 'DEPLOYED'; + +/** @deprecated this component will be removed because it's unused */ +@Component({ + selector: 'adf-cloud-app-list', + imports: [ + CommonModule, + TranslatePipe, + AppDetailsCloudComponent, + MatIconModule, + MatLineModule, + MatListModule, + EmptyContentComponent, + MatProgressSpinnerModule + ], + templateUrl: './app-list-cloud.component.html', + styleUrls: ['./app-list-cloud.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class AppListCloudComponent implements OnInit, AfterContentInit { + @ContentChild(CustomEmptyContentTemplateDirective) + emptyCustomContent: CustomEmptyContentTemplateDirective; + + /** + * Defines the layout of the apps. There are two possible + * values, "GRID" and "LIST". + */ + @Input() + layoutType: string = LAYOUT_GRID; + + /** Emitted when an app entry is clicked. */ + @Output() + appClick = new EventEmitter(); + + apps$: Observable; + loadingError$ = new Subject(); + hasEmptyCustomContentTemplate: boolean = false; + + constructor(private appsProcessCloudService: AppsProcessCloudService) {} + + ngOnInit() { + if (!this.isValidType()) { + this.setDefaultLayoutType(); + } + + this.apps$ = this.appsProcessCloudService.getDeployedApplicationsByStatus(DEPLOYED_STATUS).pipe( + catchError(() => { + this.loadingError$.next(true); + return of(); + }) + ); + } + + ngAfterContentInit() { + if (this.emptyCustomContent) { + this.hasEmptyCustomContentTemplate = true; + } + } + + onSelectApp(app: ApplicationInstanceModel): void { + this.appClick.emit(app); + } + + /** + * Check if the value of the layoutType property is an allowed value + * + * @returns `true` if layout type is valid, otherwise `false` + */ + isValidType(): boolean { + if (this.layoutType && (this.layoutType === LAYOUT_LIST || this.layoutType === LAYOUT_GRID)) { + return true; + } + return false; + } + + /** + * Assign the default value to LayoutType + */ + setDefaultLayoutType(): void { + this.layoutType = LAYOUT_GRID; + } + + /** + * Check if the layout type is LIST + * + * @returns `true` if the layout is list, otherwise `false` + */ + isList(): boolean { + return this.layoutType === LAYOUT_LIST; + } + + /** + * Check if the layout type is GRID + * + * @returns `true` if layout is grid, otherwise `false` + */ + isGrid(): boolean { + return this.layoutType === LAYOUT_GRID; + } +} diff --git a/lib/process-services-cloud/src/lib/app/models/application-instance.model.ts b/lib/process-services-cloud/src/lib/app/models/application-instance.model.ts index ec3c54ef49..9ce231a254 100644 --- a/lib/process-services-cloud/src/lib/app/models/application-instance.model.ts +++ b/lib/process-services-cloud/src/lib/app/models/application-instance.model.ts @@ -15,6 +15,9 @@ * limitations under the License. */ +export const DEFAULT_APP_INSTANCE_THEME = 'theme-2'; +export const DEFAULT_APP_INSTANCE_ICON = 'favorite_border'; + export interface ApplicationInstanceModel { name?: string; displayName?: string; diff --git a/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.html b/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.html new file mode 100644 index 0000000000..a65729f038 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.html @@ -0,0 +1,7 @@ + + {{'ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.FORM' | translate}} + + {{'ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.NONE' | translate}} + {{ form.name }} + + diff --git a/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.scss b/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.scss new file mode 100644 index 0000000000..d5db4f165d --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.scss @@ -0,0 +1,5 @@ +.adf { + &-form-definition-selector { + width: 100%; + } +} diff --git a/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.spec.ts new file mode 100644 index 0000000000..3d76891b92 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.spec.ts @@ -0,0 +1,79 @@ +/*! + * @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 { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormDefinitionSelectorCloudComponent } from './form-definition-selector-cloud.component'; +import { of } from 'rxjs'; +import { FormDefinitionSelectorCloudService } from '../services/form-definition-selector-cloud.service'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatSelectHarness } from '@angular/material/select/testing'; +import { NoopTranslateModule } from '@alfresco/adf-core'; + +describe('FormDefinitionCloudComponent', () => { + let fixture: ComponentFixture; + let service: FormDefinitionSelectorCloudService; + let getFormsSpy: jasmine.Spy; + let loader: HarnessLoader; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [NoopTranslateModule, FormDefinitionSelectorCloudComponent] + }); + fixture = TestBed.createComponent(FormDefinitionSelectorCloudComponent); + service = TestBed.inject(FormDefinitionSelectorCloudService); + getFormsSpy = spyOn(service, 'getStandAloneTaskForms').and.returnValue(of([{ id: 'fake-form', name: 'fakeForm' } as any])); + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should load the forms by default', async () => { + const selectElement = await loader.getHarness(MatSelectHarness.with({ selector: '.adf-form-selector-dropdown' })); + await selectElement.open(); + const options = await selectElement.getOptions(); + + expect(options.length).toBe(2); + expect(await options[0].getText()).toBe('ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.NONE'); + expect(await options[1].getText()).toBe('fakeForm'); + expect(getFormsSpy).toHaveBeenCalled(); + }); + + it('should load only None option when no forms exist', async () => { + getFormsSpy.and.returnValue(of([])); + const selectElement = await loader.getHarness(MatSelectHarness.with({ selector: '.adf-form-selector-dropdown' })); + await selectElement.open(); + + const options = await selectElement.getOptions(); + + expect(options.length).toBe(1); + }); + + it('should not preselect any form by default', async () => { + const selectElement = await loader.getHarness(MatSelectHarness.with({ selector: '.adf-form-selector-dropdown' })); + + expect(await selectElement.getValueText()).toBe(''); + }); + + it('should display the name of the form that is selected', async () => { + const selectElement = await loader.getHarness(MatSelectHarness.with({ selector: '.adf-form-selector-dropdown' })); + await selectElement.open(); + const options = await selectElement.getOptions(); + + await options[1].click(); + + expect(await selectElement.getValueText()).toBe('fakeForm'); + }); +}); diff --git a/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.ts b/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.ts new file mode 100644 index 0000000000..77e91c0afe --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/components/form-definition-selector-cloud.component.ts @@ -0,0 +1,53 @@ +/*! + * @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 { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Observable } from 'rxjs'; +import { FormDefinitionSelectorCloudService } from '../services/form-definition-selector-cloud.service'; +import { MatSelectChange, MatSelectModule } from '@angular/material/select'; +import { FormRepresentation } from '../../services/form-fields.interfaces'; +import { CommonModule } from '@angular/common'; +import { TranslatePipe } from '@ngx-translate/core'; + +/** @deprecated this component will be removed because it's unused */ +@Component({ + selector: 'adf-cloud-form-definition-selector', + imports: [CommonModule, TranslatePipe, MatSelectModule], + templateUrl: './form-definition-selector-cloud.component.html', + styleUrls: ['./form-definition-selector-cloud.component.scss'] +}) +export class FormDefinitionSelectorCloudComponent implements OnInit { + /** Name of the application. If specified, this shows the users who have access to the app. */ + @Input() + appName: string = ''; + + /** Emitted when a form is selected. */ + @Output() + selectForm: EventEmitter = new EventEmitter(); + + forms$: Observable; + + constructor(private formDefinitionCloudService: FormDefinitionSelectorCloudService) {} + + ngOnInit(): void { + this.forms$ = this.formDefinitionCloudService.getStandAloneTaskForms(this.appName); + } + + onSelect(event: MatSelectChange) { + this.selectForm.emit(event.value); + } +} diff --git a/lib/process-services-cloud/src/lib/form/mocks/form-representation.mock.ts b/lib/process-services-cloud/src/lib/form/mocks/form-representation.mock.ts new file mode 100644 index 0000000000..bc771b4ce0 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/mocks/form-representation.mock.ts @@ -0,0 +1,45 @@ +/*! + * @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. + */ + +export const mockFormRepresentations = [ + { + formRepresentation: { + id: 'form-de8895be-d0d7-4434-beef-559b15305d72', + name: 'Form 1', + description: '', + version: 0, + standalone: true + } + }, + { + formRepresentation: { + id: 'form-de8895be-d0d7-4434-beef-fgr34ttgrtgd', + name: 'Form 2', + description: '', + version: 0, + standalone: false + } + }, + { + formRepresentation: { + id: 'form-de8895be-d0d7-4434-beef-53453453452', + name: 'Form 3', + description: '', + version: 0 + } + } +]; diff --git a/lib/process-services-cloud/src/lib/form/services/form-definition-selector-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/form/services/form-definition-selector-cloud.service.spec.ts new file mode 100644 index 0000000000..f7458429a6 --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/form-definition-selector-cloud.service.spec.ts @@ -0,0 +1,55 @@ +/*! + * @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 { TestBed } from '@angular/core/testing'; +import { FormDefinitionSelectorCloudService } from './form-definition-selector-cloud.service'; +import { mockFormRepresentations } from '../mocks/form-representation.mock'; +import { AdfHttpClient } from '@alfresco/adf-core/api'; +import { NoopTranslateModule } from '@alfresco/adf-core'; + +describe('Form Definition Selector Cloud Service', () => { + let service: FormDefinitionSelectorCloudService; + let adfHttpClient: AdfHttpClient; + const appName = 'app-name'; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [NoopTranslateModule] + }); + service = TestBed.inject(FormDefinitionSelectorCloudService); + adfHttpClient = TestBed.inject(AdfHttpClient); + spyOn(adfHttpClient, 'request').and.returnValue(Promise.resolve(mockFormRepresentations)); + }); + + it('should fetch all the forms when getForms is called', (done) => { + service.getForms(appName).subscribe((result) => { + expect(result).toBeDefined(); + expect(result.length).toBe(3); + done(); + }); + }); + + it('should fetch only standalone enabled forms when getStandaloneTaskForms is called', (done) => { + service.getStandAloneTaskForms(appName).subscribe((result) => { + expect(result).toBeDefined(); + expect(result.length).toBe(2); + expect(result[0].name).toBe('Form 1'); + expect(result[1].name).toBe('Form 3'); + done(); + }); + }); +}); diff --git a/lib/process-services-cloud/src/lib/form/services/form-definition-selector-cloud.service.ts b/lib/process-services-cloud/src/lib/form/services/form-definition-selector-cloud.service.ts new file mode 100644 index 0000000000..d9f3752dab --- /dev/null +++ b/lib/process-services-cloud/src/lib/form/services/form-definition-selector-cloud.service.ts @@ -0,0 +1,53 @@ +/*! + * @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 { map } from 'rxjs/operators'; +import { from, Observable } from 'rxjs'; +import { BaseCloudService } from '../../services/base-cloud.service'; +import { FormRepresentation } from '../../services/form-fields.interfaces'; +import { FormDefinitionSelectorCloudServiceInterface } from './form-definition-selector-cloud.service.interface'; + +/** @deprecated this service will be removed because it's component is unused */ +@Injectable({ + providedIn: 'root' +}) +export class FormDefinitionSelectorCloudService extends BaseCloudService implements FormDefinitionSelectorCloudServiceInterface { + /** + * Get all forms of an app. + * + * @param appName Name of the application + * @returns Details of the forms + */ + getForms(appName: string): Observable { + const url = `${this.getBasePath(appName)}/form/v1/forms`; + + return this.get(url).pipe(map((data: any) => data.map((formData: any) => formData.formRepresentation))); + } + + /** + * Get all forms of an app. + * + * @param appName Name of the application + * @returns Details of the forms + */ + getStandAloneTaskForms(appName: string): Observable { + return from(this.getForms(appName)).pipe( + map((data: any) => data.filter((formData: any) => formData.standalone || formData.standalone === undefined)) + ); + } +} diff --git a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts index 11d6bb57aa..65b812c7a9 100644 --- a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts @@ -17,6 +17,7 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; import { provideTranslations } from '@alfresco/adf-core'; +import { APP_LIST_CLOUD_DIRECTIVES } from './app/app-list-cloud.module'; import { TaskCloudModule } from './task/task-cloud.module'; import { ProcessCloudModule } from './process/process-cloud.module'; import { FORM_CLOUD_DIRECTIVES } from './form/form-cloud.module'; @@ -27,7 +28,12 @@ import { PeopleCloudComponent } from './people/components/people-cloud.component import { provideCloudFormRenderer, provideCloudPreferences } from './providers'; import { TaskListCloudService } from './task/task-list/services/task-list-cloud.service'; -export const PROCESS_SERVICES_CLOUD_DIRECTIVES = [...FORM_CLOUD_DIRECTIVES, ...TASK_FORM_CLOUD_DIRECTIVES, PeopleCloudComponent] as const; +export const PROCESS_SERVICES_CLOUD_DIRECTIVES = [ + ...APP_LIST_CLOUD_DIRECTIVES, + ...FORM_CLOUD_DIRECTIVES, + ...TASK_FORM_CLOUD_DIRECTIVES, + PeopleCloudComponent +] as const; /** * @deprecated this module is deprecated and will be removed in the future versions