From 417507e2858dd3f1b38de08362610a15037401f2 Mon Sep 17 00:00:00 2001 From: Xaviju Date: Mon, 18 Sep 2023 17:16:11 +0200 Subject: [PATCH] feat(workflows): create workflow page --- .../+state/actions/project.actions.ts | 9 +++ .../+state/actions/kanban.actions.ts | 1 + .../+state/effects/kanban.effects.ts | 4 +- .../project-feature-kanban.component.ts | 35 ++++++++--- .../project-navigation-menu.component.html | 6 +- .../project-navigation-menu.component.ts | 4 -- ...oject-feature-new-kanban-routing.module.ts | 21 +++++++ .../project-feature-new-kanban.component.css | 7 +++ .../project-feature-new-kanban.component.html | 9 +++ ...oject-feature-new-kanban.component.spec.ts | 63 +++++++++++++++++++ .../project-feature-new-kanban.component.ts | 22 +++++++ .../project-feature-new-kanban.module.ts | 19 ++++++ .../project-feature-shell-routing.module.ts | 31 +++++++++ .../project-feature-shell.component.ts | 14 +++-- 14 files changed, 222 insertions(+), 23 deletions(-) create mode 100644 javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban-routing.module.ts create mode 100644 javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.css create mode 100644 javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.html create mode 100644 javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.spec.ts create mode 100644 javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.ts create mode 100644 javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.module.ts diff --git a/javascript/apps/taiga/src/app/modules/project/data-access/+state/actions/project.actions.ts b/javascript/apps/taiga/src/app/modules/project/data-access/+state/actions/project.actions.ts index 3bc15d2ff..c9e7d1407 100644 --- a/javascript/apps/taiga/src/app/modules/project/data-access/+state/actions/project.actions.ts +++ b/javascript/apps/taiga/src/app/modules/project/data-access/+state/actions/project.actions.ts @@ -16,6 +16,7 @@ import { StoryDetail, User, UserComment, + Workflow, } from '@taiga/data'; import { DropCandidate } from '@taiga/ui/drag/drag.model'; @@ -67,6 +68,14 @@ export const updateStoryShowView = createAction( }>() ); +export const createWorkflow = createAction( + '[Project] Create Workflow', + props<{ + project: Project; + name: Workflow['name']; + }> +); + export const newProjectMembers = createAction( '[Project][ws] New Project Members' ); diff --git a/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/actions/kanban.actions.ts b/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/actions/kanban.actions.ts index 41a3bbd46..e53685fc4 100644 --- a/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/actions/kanban.actions.ts +++ b/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/actions/kanban.actions.ts @@ -20,6 +20,7 @@ export const KanbanActions = createActionGroup({ source: 'Kanban', events: { 'Init Kanban': props<{ workflow: Workflow['slug'] }>(), + 'Load Workflow kanban': props<{ workflow: Workflow['slug'] }>(), 'Open Create Story form': props<{ status: Status['id'] }>(), 'Close Create Story form': emptyProps(), 'Create Story': props<{ diff --git a/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/effects/kanban.effects.ts b/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/effects/kanban.effects.ts index 760212afd..c43352087 100644 --- a/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/effects/kanban.effects.ts +++ b/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/effects/kanban.effects.ts @@ -44,7 +44,7 @@ import { export class KanbanEffects { public loadKanbanWorkflows$ = createEffect(() => { return this.actions$.pipe( - ofType(KanbanActions.initKanban), + ofType(KanbanActions.initKanban, KanbanActions.loadWorkflowKanban), concatLatestFrom(() => [ this.store.select(selectCurrentProject).pipe(filterNil()), ]), @@ -67,7 +67,7 @@ export class KanbanEffects { public loadKanbanStories$ = createEffect(() => { return this.actions$.pipe( - ofType(KanbanActions.initKanban), + ofType(KanbanActions.initKanban, KanbanActions.loadWorkflowKanban), concatLatestFrom(() => [ this.store.select(selectCurrentProject).pipe(filterNil()), this.store.select(selectWorkflow), diff --git a/javascript/apps/taiga/src/app/modules/project/feature-kanban/project-feature-kanban.component.ts b/javascript/apps/taiga/src/app/modules/project/feature-kanban/project-feature-kanban.component.ts index 0c2d520f0..3ed2ceb0d 100644 --- a/javascript/apps/taiga/src/app/modules/project/feature-kanban/project-feature-kanban.component.ts +++ b/javascript/apps/taiga/src/app/modules/project/feature-kanban/project-feature-kanban.component.ts @@ -30,7 +30,16 @@ import { WorkflowStatus, } from '@taiga/data'; import { ModalComponent } from '@taiga/ui/modal/components'; -import { combineLatest, filter, map, merge, pairwise, take } from 'rxjs'; +import { + combineLatest, + distinctUntilChanged, + filter, + map, + merge, + pairwise, + skip, + take, +} from 'rxjs'; import * as ProjectActions from '~/app/modules/project/data-access/+state/actions/project.actions'; import { selectCurrentProject, @@ -134,16 +143,22 @@ export class ProjectFeatureKanbanComponent { return; } - this.route.paramMap.subscribe((params) => { - const workflowSlug = params.get('workflow') ?? 'main'; - this.store.dispatch(KanbanActions.initKanban({ workflow: workflowSlug })); - }); - // Load on init kanban page. Not on every reload - // const workflowSlug = this.route.snapshot.params['workflow']; - // this.store.dispatch( - // KanbanActions.initKanban({ workflow: workflowSlug }) - // ); + const workflow: Workflow['slug'] = + (this.route.snapshot.params['workflow'] as Workflow['slug']) ?? 'main'; + this.store.dispatch(KanbanActions.initKanban({ workflow })); + + this.route.paramMap + .pipe( + map((params) => { + return params.get('workflow') ?? 'main'; + }), + distinctUntilChanged(), + skip(1) + ) + .subscribe((workflow: Workflow['slug']) => { + this.store.dispatch(KanbanActions.loadWorkflowKanban({ workflow })); + }); this.checkInviteModalStatus(); this.state.connect( diff --git a/javascript/apps/taiga/src/app/modules/project/feature-navigation/components/project-navigation-menu/project-navigation-menu.component.html b/javascript/apps/taiga/src/app/modules/project/feature-navigation/components/project-navigation-menu/project-navigation-menu.component.html index 3f366bcd5..a08f62a0e 100644 --- a/javascript/apps/taiga/src/app/modules/project/feature-navigation/components/project-navigation-menu/project-navigation-menu.component.html +++ b/javascript/apps/taiga/src/app/modules/project/feature-navigation/components/project-navigation-menu/project-navigation-menu.component.html @@ -394,7 +394,8 @@ - + icon="plus"> diff --git a/javascript/apps/taiga/src/app/modules/project/feature-navigation/components/project-navigation-menu/project-navigation-menu.component.ts b/javascript/apps/taiga/src/app/modules/project/feature-navigation/components/project-navigation-menu/project-navigation-menu.component.ts index 6af7f0f5c..66b24fe5e 100644 --- a/javascript/apps/taiga/src/app/modules/project/feature-navigation/components/project-navigation-menu/project-navigation-menu.component.ts +++ b/javascript/apps/taiga/src/app/modules/project/feature-navigation/components/project-navigation-menu/project-navigation-menu.component.ts @@ -213,10 +213,6 @@ export class ProjectNavigationMenuComponent { this.dialog.type = ''; } - public createWorkflow() { - console.log('create workflow'); - } - public trackById(_index: number, workflow: Workflow) { return workflow.id; } diff --git a/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban-routing.module.ts b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban-routing.module.ts new file mode 100644 index 000000000..0c6c7e4d1 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban-routing.module.ts @@ -0,0 +1,21 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2023-present Kaleidos INC + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { ProjectFeatureNewKanbanComponent } from './project-feature-new-kanban.component'; + +const routes: Routes = [ + { path: '', component: ProjectFeatureNewKanbanComponent }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class ProjectFeatureNewKanbanRoutingModule {} diff --git a/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.css b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.css new file mode 100644 index 000000000..711742e35 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.css @@ -0,0 +1,7 @@ +/* +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Copyright (c) 2023-present Kaleidos INC +*/ diff --git a/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.html b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.html new file mode 100644 index 000000000..51250bb62 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.html @@ -0,0 +1,9 @@ + + +
New Kanban Works!
diff --git a/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.spec.ts b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.spec.ts new file mode 100644 index 000000000..3de00cc2f --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.spec.ts @@ -0,0 +1,63 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2023-present Kaleidos INC + */ + +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { Spectator, createComponentFactory } from '@ngneat/spectator/jest'; +import { getTranslocoModule } from '~/app/transloco/transloco-testing.module'; +import { provideMockStore, MockStore } from '@ngrx/store/testing'; +import { ProjectFeatureNewKanbanComponent } from './project-feature-new-kanban.component'; + +import { selectorExample } from './+state/selectors/project-feature-new-kanban.selectors'; + +describe('ProjectFeatureNewKanbanComponent', () => { + let spectator: Spectator; + let store: MockStore; + + const createComponent = createComponentFactory({ + component: ProjectFeatureNewKanbanComponent, + imports: [getTranslocoModule()], + schemas: [NO_ERRORS_SCHEMA], + mocks: [], + }); + + const initialState = { loggedIn: false }; + + beforeEach(() => { + spectator = createComponent({ + // The component inputs + props: { + name: 'example', + }, + // Override the component's providers + providers: [provideMockStore({ initialState })], + // Whether to run change detection (defaults to true) + detectChanges: false, + }); + + store = spectator.inject(MockStore); + }); + + it('example', () => { + // change ngrx state + store.setState({ loggedIn: true }); + + // mock selector + store.overrideSelector(selectorExample, { + id: 1, + name: 'test', + }); + + // trigger emission from all selectors + store.refreshState(); + + spectator.detectChanges(); + + // This test checks that the input attribute name becomes a class in the component structure + expect(spectator.query('div')).toHaveClass('example'); + }); +}); diff --git a/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.ts b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.ts new file mode 100644 index 000000000..d66572e09 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.component.ts @@ -0,0 +1,22 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2023-present Kaleidos INC + */ + +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'tg-project-feature-new-kanban', + templateUrl: './project-feature-new-kanban.component.html', + styleUrls: ['./project-feature-new-kanban.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, +}) +export class ProjectFeatureNewKanbanComponent implements OnInit { + public ngOnInit(): void { + console.log('ProjectFeatureNewKanbanComponent works!'); + } +} diff --git a/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.module.ts b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.module.ts new file mode 100644 index 000000000..789e74603 --- /dev/null +++ b/javascript/apps/taiga/src/app/modules/project/feature-new-kanban/project-feature-new-kanban.module.ts @@ -0,0 +1,19 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright (c) 2023-present Kaleidos INC + */ + +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ProjectFeatureNewKanbanRoutingModule } from './project-feature-new-kanban-routing.module'; + +@NgModule({ + imports: [CommonModule, ProjectFeatureNewKanbanRoutingModule], + declarations: [], + providers: [], + exports: [], +}) +export class ProjectFeatureNewKanbanModule {} diff --git a/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell-routing.module.ts b/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell-routing.module.ts index 170a8efc0..f9909a921 100644 --- a/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell-routing.module.ts +++ b/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell-routing.module.ts @@ -28,6 +28,26 @@ const routes: Routes = [ '~/app/modules/project/feature-overview/project-feature-overview.module' ).then((m) => m.ProjectFeatureOverviewModule), }, + { + path: 'overview', + loadChildren: () => + import( + '~/app/modules/project/feature-overview/project-feature-overview.module' + ).then((m) => m.ProjectFeatureOverviewModule), + data: { + overview: true, + }, + }, + { + path: ':slug/overview', + loadChildren: () => + import( + '~/app/modules/project/feature-overview/project-feature-overview.module' + ).then((m) => m.ProjectFeatureOverviewModule), + data: { + overview: true, + }, + }, { path: 'kanban', loadChildren: () => @@ -48,6 +68,17 @@ const routes: Routes = [ redirectTo: ':slug/kanban/main', pathMatch: 'full', }, + { + path: ':slug/new-kanban', + loadChildren: () => + import( + '~/app/modules/project/feature-new-kanban/project-feature-new-kanban.module' + ).then((m) => m.ProjectFeatureNewKanbanModule), + canDeactivate: [CanDeactivateGuard], + data: { + newKanban: true, + }, + }, { path: ':slug/kanban/:workflow', loadChildren: () => diff --git a/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell.component.ts b/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell.component.ts index a54d0fd27..f64ce4620 100644 --- a/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell.component.ts +++ b/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell.component.ts @@ -15,15 +15,15 @@ import { OnDestroy, } from '@angular/core'; import { - Router, - RouterOutlet, ActivatedRoute, ActivatedRouteSnapshot, + Router, + RouterOutlet, } from '@angular/router'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { Store } from '@ngrx/store'; import { RxState } from '@rx-angular/state'; -import { TuiNotification, TuiButtonModule } from '@taiga-ui/core'; +import { TuiButtonModule, TuiNotification } from '@taiga-ui/core'; import { Attachment, Membership, @@ -54,9 +54,9 @@ import { } from '../data-access/+state/actions/project.actions'; import { setNotificationClosed } from '../feature-overview/data-access/+state/actions/project-overview.actions'; -import { ProjectNavigationComponent } from '../feature-navigation/project-feature-navigation.component'; import { TranslocoDirective } from '@ngneat/transloco'; import { ContextNotificationComponent } from '@taiga/ui/context-notification/context-notification.component'; +import { ProjectNavigationComponent } from '../feature-navigation/project-feature-navigation.component'; @UntilDestroy() @Component({ @@ -153,12 +153,18 @@ export class ProjectFeatureShellComponent implements OnDestroy, AfterViewInit { active.routeConfig?.component?.name === 'ProjectFeatureOverviewComponent'; const isSettings = !!active.data.settings; + const isNewKanban = !!active.data.newKanban; if (isKanban) { void this.router.navigate( [`project/${project.id}/${project.slug}/kanban`], { replaceUrl: true } ); + } else if (isNewKanban) { + void this.router.navigate( + [`project/${project.id}/${project.slug}/new-kanban`], + { replaceUrl: true } + ); } else if (isOverview) { void this.router.navigate( [`project/${project.id}/${project.slug}/overview`],