From 875c7dfe720bb877ba98d71dd3b4446a8c17e214 Mon Sep 17 00:00:00 2001 From: Nikolay Yurchenko Date: Tue, 15 Oct 2019 16:22:41 +0300 Subject: [PATCH] web/satellite: project members frontend selection caching added (#3238) --- .../src/components/team/HeaderArea.vue | 18 +++++---- .../components/team/ProjectMembersArea.vue | 15 ++++--- .../src/store/modules/projectMembers.ts | 40 ++++++++++++++----- web/satellite/src/types/projectMembers.ts | 4 +- .../__snapshots__/HeaderArea.spec.ts.snap | 6 +-- .../tests/unit/store/projectMembers.spec.ts | 40 +++++++++++++++++-- 6 files changed, 92 insertions(+), 31 deletions(-) diff --git a/web/satellite/src/components/team/HeaderArea.vue b/web/satellite/src/components/team/HeaderArea.vue index 70f4d1fe6..251038ee6 100644 --- a/web/satellite/src/components/team/HeaderArea.vue +++ b/web/satellite/src/components/team/HeaderArea.vue @@ -35,9 +35,10 @@ is-white="true" :on-press="onClearSelection" /> + {{selectedProjectMembersCount}} users selected
- Are you sure you want to delete {{selectedProjectMembersCount}} {{userCountTitle}}? + Are you sure you want to delete {{selectedProjectMembersCount}} {{userCountTitle}}?
{ - const projectMemberEmails: string[] = this.$store.getters.selectedProjectMembers.map((member: ProjectMember) => { - return member.user.email; - }); - try { - await this.$store.dispatch(PM_ACTIONS.DELETE, projectMemberEmails); + await this.$store.dispatch(PM_ACTIONS.DELETE); } catch (error) { this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Error while deleting users from projectMembers. ${error.message}`); return; } + this.$emit('onSuccessAction'); this.$store.dispatch(NOTIFICATION_ACTIONS.SUCCESS, 'Members was successfully removed from project'); this.isDeleteClicked = false; @@ -212,6 +211,11 @@ export default class HeaderArea extends Vue { align-items: flex-end; height: 85px; justify-content: center; + + &__info-text { + margin-left: 25px; + line-height: 48px; + } } .button { diff --git a/web/satellite/src/components/team/ProjectMembersArea.vue b/web/satellite/src/components/team/ProjectMembersArea.vue index 36f741c5d..8570f6267 100644 --- a/web/satellite/src/components/team/ProjectMembersArea.vue +++ b/web/satellite/src/components/team/ProjectMembersArea.vue @@ -6,7 +6,8 @@
@@ -99,7 +100,7 @@ export default class ProjectMembersArea extends Vue { } public onMemberClick(member: ProjectMember): void { - this.$store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, member.user.id); + this.$store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, member); } public get projectMembers(): ProjectMember[] { @@ -122,12 +123,12 @@ export default class ProjectMembersArea extends Vue { return this.$store.state.projectMembersModule.page.pageCount; } - public get selectedProjectMembers(): ProjectMember[] { - return this.$store.getters.selectedProjectMembers; + public get selectedProjectMembersLength(): number { + return this.$store.state.projectMembersModule.selectedProjectMembersEmails.length; } public get headerState(): number { - if (this.selectedProjectMembers.length > 0) { + if (this.selectedProjectMembersLength > 0) { return ProjectMemberHeaderState.ON_SELECT; } @@ -159,6 +160,10 @@ export default class ProjectMembersArea extends Vue { await this.$store.dispatch(NOTIFICATION_ACTIONS.ERROR, `Unable to fetch project members. ${error.message}`); } + this.resetPaginator(); + } + + public resetPaginator(): void { if (this.totalPageCount > 1) { this.$refs.pagination.resetPageIndex(); } diff --git a/web/satellite/src/store/modules/projectMembers.ts b/web/satellite/src/store/modules/projectMembers.ts index d374486d7..bd60d65a2 100644 --- a/web/satellite/src/store/modules/projectMembers.ts +++ b/web/satellite/src/store/modules/projectMembers.ts @@ -36,6 +36,7 @@ const { class ProjectMembersState { public cursor: ProjectMemberCursor = new ProjectMemberCursor(); public page: ProjectMembersPage = new ProjectMembersPage(); + public selectedProjectMembersEmails: string[] = []; } export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule { @@ -44,6 +45,13 @@ export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule { + if (state.selectedProjectMembersEmails.includes(member.user.email)) { + member.isSelected = true; + } + + return member; + }); }, [SET_PAGE](state: ProjectMembersState, page: number) { state.cursor.page = page; @@ -60,17 +68,23 @@ export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule { - if (projectMember.user.id === projectMemberId) { - projectMember.isSelected = !projectMember.isSelected; - } + [TOGGLE_SELECTION](state: ProjectMembersState, projectMember: ProjectMember) { + if (!state.selectedProjectMembersEmails.includes(projectMember.user.email)) { + projectMember.isSelected = true; + state.selectedProjectMembersEmails.push(projectMember.user.email); - return projectMember; + return; + } + + projectMember.isSelected = false; + state.selectedProjectMembersEmails = state.selectedProjectMembersEmails.filter(projectMemberEmail => { + return projectMemberEmail !== projectMember.user.email; }); }, [CLEAR_SELECTION](state: ProjectMembersState) { + state.selectedProjectMembersEmails = []; state.page.projectMembers = state.page.projectMembers.map((projectMember: ProjectMember) => { projectMember.isSelected = false; @@ -84,10 +98,12 @@ export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule { + deleteProjectMembers: async function ({rootGetters, state, commit}: any): Promise { const projectId = rootGetters.selectedProject.id; - await api.delete(projectId, projectMemberEmails); + await api.delete(projectId, state.selectedProjectMembersEmails); + + commit(CLEAR_SELECTION); }, fetchProjectMembers: async function ({commit, rootGetters, state}: any, page: number): Promise { const projectID = rootGetters.selectedProject.id; @@ -112,15 +128,17 @@ export function makeProjectMembersModule(api: ProjectMembersApi): StoreModule state.page.projectMembers.filter((member: ProjectMember) => member.isSelected), + selectedProjectMembers: (state: ProjectMembersState) => + state.page.projectMembers.filter((member: ProjectMember) => + state.selectedProjectMembersEmails.includes(member.user.email)), }, }; } diff --git a/web/satellite/src/types/projectMembers.ts b/web/satellite/src/types/projectMembers.ts index 2c45ed84f..67b574f24 100644 --- a/web/satellite/src/types/projectMembers.ts +++ b/web/satellite/src/types/projectMembers.ts @@ -93,8 +93,8 @@ export class ProjectMember { public joinedAt: string; public isSelected: boolean; - public constructor(fullName: string, shortName: string, email: string, joinedAt: string, id?: string) { - this.user = new User(id || '', fullName, shortName, email); + public constructor(fullName: string, shortName: string, email: string, joinedAt: string, id: string = '') { + this.user = new User(id, fullName, shortName, email); this.joinedAt = joinedAt; this.isSelected = false; } diff --git a/web/satellite/tests/unit/projectMembers/__snapshots__/HeaderArea.spec.ts.snap b/web/satellite/tests/unit/projectMembers/__snapshots__/HeaderArea.spec.ts.snap index 9080f32c1..5f63df2cf 100644 --- a/web/satellite/tests/unit/projectMembers/__snapshots__/HeaderArea.spec.ts.snap +++ b/web/satellite/tests/unit/projectMembers/__snapshots__/HeaderArea.spec.ts.snap @@ -25,7 +25,7 @@ exports[`Team HeaderArea renders correctly with 1 selected user and delete click -
Are you sure you want to delete 1 user? +
Are you sure you want to delete 1 user?
@@ -46,7 +46,7 @@ exports[`Team HeaderArea renders correctly with 2 selected users and delete clic -
Are you sure you want to delete 2 users? +
Are you sure you want to delete 2 users?
@@ -86,7 +86,7 @@ exports[`Team HeaderArea renders correctly with selected users 1`] = `
- + 2 users selected
diff --git a/web/satellite/tests/unit/store/projectMembers.spec.ts b/web/satellite/tests/unit/store/projectMembers.spec.ts index fa1980fe7..f8667ca4c 100644 --- a/web/satellite/tests/unit/store/projectMembers.spec.ts +++ b/web/satellite/tests/unit/store/projectMembers.spec.ts @@ -79,9 +79,24 @@ describe('mutations', () => { }); it('toggle selection', function () { - store.commit(PROJECT_MEMBER_MUTATIONS.TOGGLE_SELECTION, projectMember1.user.id); + const testProjectMembersPage = new ProjectMembersPage(); + testProjectMembersPage.projectMembers = [projectMember1]; + testProjectMembersPage.totalCount = 1; + testProjectMembersPage.pageCount = 1; + + store.commit(PROJECT_MEMBER_MUTATIONS.TOGGLE_SELECTION, projectMember1); expect(state.page.projectMembers[0].isSelected).toBe(true); + expect(state.selectedProjectMembersEmails.length).toBe(1); + + store.commit(PROJECT_MEMBER_MUTATIONS.FETCH, testProjectMembersPage); + + expect(state.selectedProjectMembersEmails.length).toBe(1); + + store.commit(PROJECT_MEMBER_MUTATIONS.TOGGLE_SELECTION, projectMember1); + + expect(state.page.projectMembers[0].isSelected).toBe(false); + expect(state.selectedProjectMembersEmails.length).toBe(0); }); it('clear selection', function () { @@ -90,6 +105,8 @@ describe('mutations', () => { state.page.projectMembers.forEach((pm: ProjectMember) => { expect(pm.isSelected).toBe(false); }); + + expect(state.selectedProjectMembersEmails.length).toBe(0); }); it('clear store', function () { @@ -100,6 +117,7 @@ describe('mutations', () => { expect(state.cursor.order).toBe(ProjectMemberOrderBy.NAME); expect(state.cursor.orderDirection).toBe(SortDirection.ASCENDING); expect(state.page.projectMembers.length).toBe(0); + expect(state.selectedProjectMembersEmails.length).toBe(0); }); }); @@ -243,9 +261,25 @@ describe('actions', async () => { ); await store.dispatch(PM_ACTIONS.FETCH, FIRST_PAGE); - store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, projectMember1.user.id); + store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, projectMember1); expect(state.page.projectMembers[0].isSelected).toBe(true); + expect(state.selectedProjectMembersEmails.length).toBe(1); + + store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, projectMember2); + + expect(state.page.projectMembers[1].isSelected).toBe(true); + expect(state.selectedProjectMembersEmails.length).toBe(2); + + await store.dispatch(PM_ACTIONS.FETCH, FIRST_PAGE); + + expect(state.page.projectMembers[1].isSelected).toBe(true); + expect(state.selectedProjectMembersEmails.length).toBe(2); + + store.dispatch(PM_ACTIONS.TOGGLE_SELECTION, projectMember1); + + expect(state.page.projectMembers[0].isSelected).toBe(false); + expect(state.selectedProjectMembersEmails.length).toBe(1); }); it('clear selection', function () { @@ -269,7 +303,6 @@ describe('actions', async () => { describe('getters', () => { const selectedProjectMember = new ProjectMember('testFullName2', 'testShortName2', 'test2@example.com', 'now2', '2'); - selectedProjectMember.isSelected = true; it('selected project members', function () { const testProjectMembersPage = new ProjectMembersPage(); @@ -278,6 +311,7 @@ describe('getters', () => { testProjectMembersPage.pageCount = 1; store.commit(PROJECT_MEMBER_MUTATIONS.FETCH, testProjectMembersPage); + store.commit(PROJECT_MEMBER_MUTATIONS.TOGGLE_SELECTION, selectedProjectMember); const retrievedProjectMembers = store.getters.selectedProjectMembers;