Skip to content

Commit

Permalink
fix: t#2860 workflow redirections
Browse files Browse the repository at this point in the history
  • Loading branch information
juanfran committed Oct 20, 2023
1 parent c2dd562 commit 1f5db88
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 73 deletions.
3 changes: 2 additions & 1 deletion javascript/apps/taiga/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ function isViewSetterKanbaStory(
) {
const story = ':slug/stories/:storyRef';
const kanban = ':slug/kanban';
const workflow = ':slug/kanban/:workflow';

const urls = [story, kanban];
const urls = [story, kanban, workflow];

const findUrl = (it: ActivatedRouteSnapshot): boolean => {
const finded = !!urls.find((url) => it.routeConfig?.path === url);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Copyright (c) 2023-present Kaleidos INC
*/

import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { randUuid, randCompanyName } from '@ngneat/falso';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { provideMockActions } from '@ngrx/effects/testing';
Expand Down Expand Up @@ -40,6 +40,8 @@ import {
import { selectCurrentProject } from '../selectors/project.selectors';
import { ProjectEffects } from './project.effects';
import { HttpErrorResponse } from '@angular/common/http';
import { selectRouteParams } from '~/app/router-selectors';
import { Location } from '@angular/common';

describe('ProjectEffects', () => {
let actions$: Observable<Action>;
Expand All @@ -53,8 +55,12 @@ describe('ProjectEffects', () => {
provideMockActions(() => actions$),
{ provide: WsService, useValue: WsServiceMock },
provideMockStore({ initialState: {} }),
{
provide: Router,
useValue: { navigate: jest.fn(), url: '/some-url/kanban/old-slug' },
},
],
mocks: [ProjectApiService, AppService, Router],
mocks: [ProjectApiService, AppService, ActivatedRoute, Location],
});

beforeEach(() => {
Expand Down Expand Up @@ -330,5 +336,26 @@ describe('ProjectEffects', () => {

expect(effects.updateWorkflow$).toBeObservable(expected);
});

it('should update workflow slug in the URL', () => {
const workflow = WorkflowMockFactory();
const location = spectator.inject(Location);
const effects = spectator.inject(ProjectEffects);

const action = projectEventActions.updateWorkflow({
workflow: workflow,
});

actions$ = hot('-a', { a: action });

const params = { workflow: 'old-slug' };
store.overrideSelector(selectRouteParams, params);

expect(effects.updateWorkflowSlug$).toSatisfyOnFlush(() => {
expect(location.go).toHaveBeenCalledWith(
`/some-url/kanban/${workflow.slug}`
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { Location } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
import { fetch, pessimisticUpdate } from '@ngrx/router-store/data-persistence';
import { Store } from '@ngrx/store';
Expand All @@ -26,7 +26,7 @@ import {
} from 'rxjs/operators';
import { selectUser } from '~/app/modules/auth/data-access/+state/selectors/auth.selectors';
import * as ProjectOverviewActions from '~/app/modules/project/feature-overview/data-access/+state/actions/project-overview.actions';
import { selectUrl } from '~/app/router-selectors';
import { selectRouteParams, selectUrl } from '~/app/router-selectors';
import { AppService } from '~/app/services/app.service';
import { RevokeInvitationService } from '~/app/services/revoke-invitation.service';
import { invitationProjectActions } from '~/app/shared/invite-user-modal/data-access/+state/actions/invitation.action';
Expand Down Expand Up @@ -240,6 +240,29 @@ export class ProjectEffects {
);
});

public updateWorkflowSlug$ = createEffect(
() => {
return this.actions$.pipe(
ofType(projectEventActions.updateWorkflow),
concatLatestFrom(() => [
this.store.select(selectRouteParams).pipe(filterNil()),
]),
filter(([action, params]) => params.workflow !== action.workflow.slug),
tap(([action, params]) => {
const paramWorkflow = params.workflow as string;

void this.location.go(
this.router.url.replace(
`kanban/${paramWorkflow}`,
`kanban/${action.workflow.slug}`
)
);
})
);
},
{ dispatch: false }
);

public initAssignUser$ = createEffect(() => {
return this.actions$.pipe(
ofType(ProjectActions.initAssignUser),
Expand Down Expand Up @@ -493,6 +516,7 @@ export class ProjectEffects {
private router: Router,
private revokeInvitationService: RevokeInvitationService,
private store: Store,
private location: Location
private location: Location,
private route: ActivatedRoute
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('ProjectEffects', () => {
projectApiService.getWorkflow.mockReturnValue(cold('-b|', { b: workflow }));

actions$ = hot('-a', {
a: KanbanActions.initKanban({ workflow: workflow.slug }),
a: KanbanActions.loadWorkflowKanban({ workflow: workflow.slug }),
});

const expected = cold('--a', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
export class KanbanEffects {
public loadKanbanWorkflows$ = createEffect(() => {
return this.actions$.pipe(
ofType(KanbanActions.initKanban, KanbanActions.loadWorkflowKanban),
ofType(KanbanActions.loadWorkflowKanban),
concatLatestFrom(() => [
this.store.select(selectCurrentProject).pipe(filterNil()),
]),
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
*/

import { CommonModule, Location } from '@angular/common';
import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
Input,
ViewChild,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, Router } from '@angular/router';
import { TranslocoDirective } from '@ngneat/transloco';
Expand All @@ -30,16 +35,7 @@ import {
WorkflowStatus,
} from '@taiga/data';
import { ModalComponent } from '@taiga/ui/modal/components';
import {
combineLatest,
distinctUntilChanged,
filter,
map,
merge,
pairwise,
skip,
take,
} from 'rxjs';
import { combineLatest, filter, map, merge, pairwise, take } from 'rxjs';
import * as ProjectActions from '~/app/modules/project/data-access/+state/actions/project.actions';
import {
selectCurrentProject,
Expand Down Expand Up @@ -119,6 +115,13 @@ export class ProjectFeatureKanbanComponent {
@ViewChild(ProjectFeatureStoryWrapperSideViewComponent)
public projectFeatureStoryWrapperSideViewComponent?: ProjectFeatureStoryWrapperSideViewComponent;

@Input()
public set workflowSlug(workflow: Workflow['slug']) {
if (workflow !== this.state.get('workflow')?.slug) {
this.store.dispatch(KanbanActions.loadWorkflowKanban({ workflow }));
}
}

public invitePeopleModal = false;
public kanbanWidth = 0;
public model$ = this.state.select();
Expand Down Expand Up @@ -146,26 +149,6 @@ export class ProjectFeatureKanbanComponent {
return;
}

// Load on init kanban page. Not on every reload
const workflow: Workflow['slug'] = this.route.snapshot.params[
'workflow'
] as Workflow['slug'];
if (workflow) {
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(
'loadingWorkflow',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
</div>
</div>
<ol
[attr.aria-label]="t('commons_project.workflow_list')"
[attr.aria-label]="t('common_project.workflow.workflow_list')"
class="submenu"
*ngIf="!collapsed">
<li
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@
* Copyright (c) 2023-present Kaleidos INC
*/

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ProjectFeatureKanbanModule } from '../feature-kanban/project-feature-kanban.module';
import { Workflow } from '@taiga/data';

@Component({
selector: 'tg-project-feature-view-setter-kanban',
template: '<tg-project-feature-kanban></tg-project-feature-kanban>',
template:
'<tg-project-feature-kanban [workflowSlug]="workflowSlug"></tg-project-feature-kanban>',
styles: [''],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [ProjectFeatureKanbanModule],
})
export class ProjectFeatureViewSetterKanbanComponent {}
export class ProjectFeatureViewSetterKanbanComponent {
@Input()
public workflowSlug!: Workflow['slug'];
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ComponentRef,
DestroyRef,
OnDestroy,
ViewChild,
Expand All @@ -18,13 +19,16 @@ import {
} from '@angular/core';
import { Store } from '@ngrx/store';
import {
Observable,
combineLatest,
distinctUntilChanged,
filter,
map,
of,
pairwise,
startWith,
switchMap,
tap,
} from 'rxjs';
import { RouteHistoryService } from '~/app/shared/route-history/route-history.service';
import { StoryDetailActions } from '../story-detail/data-access/+state/actions/story-detail.actions';
Expand All @@ -43,8 +47,9 @@ import {
import { RxState } from '@rx-angular/state';
import { Project, Story, StoryDetail, StoryView, Workflow } from '@taiga/data';
import { filterNil } from '~/app/shared/utils/operators';
import { selectCurrentWorkflowSlug } from '../feature-kanban/data-access/+state/selectors/kanban.selectors';
import { ProjectFeatureStoryWrapperFullViewModule } from '../feature-story-wrapper-full-view/project-feature-story-wrapper-full-view.module';
import { selectRouteParams } from '~/app/router-selectors';
import { ProjectFeatureViewSetterKanbanComponent } from './project-feature-view-setter.component-kanban.component';

interface ProjectFeatureViewSetterComponentState {
storyView: StoryView;
Expand Down Expand Up @@ -81,6 +86,8 @@ export class ProjectFeatureViewSetterComponent implements OnDestroy {
) as RxState<ProjectFeatureViewSetterComponentState>;

public model$ = this.state.select();

private kanbanComponent?: ComponentRef<ProjectFeatureViewSetterKanbanComponent>;
private destroyRef = inject(DestroyRef);

constructor(
Expand All @@ -101,10 +108,6 @@ export class ProjectFeatureViewSetterComponent implements OnDestroy {
distinctUntilChanged()
)
);
this.state.connect(
'workflowSlug',
this.store.select(selectCurrentWorkflowSlug)
);

this.state.hold(
this.state.select('kanbanHost').pipe(distinctUntilChanged(), filterNil()),
Expand All @@ -123,20 +126,46 @@ export class ProjectFeatureViewSetterComponent implements OnDestroy {
}
);

this.state.connect(
'workflowSlug',
this.store.select(selectRouteParams).pipe(
switchMap((params): Observable<string> => {
if (params.workflow) {
return of(params.workflow as string);
}

if (params.storyRef) {
return this.store.select(selectStory).pipe(
filterNil(),
map((story) => {
return story?.workflow.slug ?? 'main';
})
);
}

return of('main');
}),
distinctUntilChanged(),
tap((workflowSlug) => {
this.kanbanComponent?.setInput('workflowSlug', workflowSlug);
})
)
);

combineLatest([
this.state.select('url'),
this.route.data,
this.route.params,
this.state.select('workflowSlug'),
])
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(([url, data, params, workflowSlug]) => {
const isKanbanUrl = url.endsWith(`/kanban/${workflowSlug}`);
this.state.connect('isKanban', of(isKanbanUrl));
.subscribe(([url, data, params]) => {
const project: Project = data.project as Project;
const isKanbanUrl = url.includes(`${project.slug}/kanban/`);
this.state.set({ isKanban: isKanbanUrl });

if (!isKanbanUrl && !!data.stories) {
const storyParams = params as StoryParams;
const needRedirect = params.slug !== (data.project as Project).slug;
const needRedirect = params.slug !== project.slug;
if (needRedirect) {
void this.router.navigate(
[
Expand Down Expand Up @@ -176,7 +205,14 @@ export class ProjectFeatureViewSetterComponent implements OnDestroy {

viewContainerRef.clear();

viewContainerRef.createComponent(ProjectFeatureViewSetterKanbanComponent);
this.kanbanComponent = viewContainerRef.createComponent(
ProjectFeatureViewSetterKanbanComponent
);

this.kanbanComponent?.setInput(
'workflowSlug',
this.state.get('workflowSlug')
);

this.cd.markForCheck();
}
Expand Down

0 comments on commit 1f5db88

Please sign in to comment.