Skip to content

Commit

Permalink
feat(VotingEventComponent): move to next step in VotingEvent flow all…
Browse files Browse the repository at this point in the history
…owed

From the admin page it is possible to move a VotingEvent to the next step
  • Loading branch information
EnricoPicci committed Jul 14, 2019
1 parent 16db542 commit c0b6ec7
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fileignoreconfig:
checksum: a433e598d534cff647c856bf63b0aee54a190fe1d25ba9458f7d48973038e4fe
ignore_detectors: []
- filename: src/app/services/backend.service.spec.ts
checksum: 2ea07ce88ac979840077d918cb3fd8d5a170c859e822e020f123982ff5789afe
checksum: e0812f8f1a6fff8d82b3257e343e6fa556d635ec1271cfa2b9ec21336187167b
ignore_detectors: []
- filename: docs/images/vote_process.gif
checksum: c3a82314b7db3029e5dd9b4226bde884f472561112b4287b5193eeeab1a7cf75
Expand Down
12 changes: 12 additions & 0 deletions src/app/models/technology.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { Comment } from './comment';

export interface VotingResults {
votesForRing: {
ring: string;
count: number; // number of votes got by a certain ring on a certain Technology and Ring
}[];
votesForTag?: {
tag: string;
count: number; // number of votes which have a certain tag
}[];
}

export interface Technology {
_id?: string;
id?: string;
Expand All @@ -12,4 +23,5 @@ export interface Technology {
comments?: Comment[];
numberOfVotes?: number;
numberOfComments?: number;
votingResult?: VotingResults;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ <h3>Single event</h3>
<button mat-flat-button color="accent" (click)="viewVoteCloud()">Word cloud</button>
<button mat-flat-button *ngIf="isSelectedEventOpen()" color="accent" (click)="voters()">Voters</button>
<button mat-flat-button *ngIf="isSelectedEventOpen()" color="accent" (click)="viewRadarForSelectedEvent()">Tech radar</button>

<span mat-flat-button *ngIf="(configuration$ | async)?.enableVotingEventFlow">
<button mat-flat-button *ngIf="isNextStepAvailable()" color="accent" (click)="goToNextStep()">{{getNextStepButtonText()}}</button>
</span>

<span mat-flat-button *ngIf="(configuration$ | async)?.revoteToggle">
<button mat-flat-button *ngIf="showTechnologiesForRevote()" color="accent" (click)="techForRevote()">Tech for
Revote</button>
Expand Down
32 changes: 32 additions & 0 deletions src/app/modules/admin/voting-event/voting-event.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ConfigurationService } from 'src/app/services/configuration.service';
import { AuthService } from '../../shared/login/auth.service';
import { EventsService } from '../../../services/events.service';
import { VoteCloudService } from '../vote-cloud/vote-cloud.service';
import { getNextActionName, getActionName, getNextAction } from 'src/app/utils/voting-event-flow.util';

@Component({
selector: 'byor-voting-event',
Expand Down Expand Up @@ -244,4 +245,35 @@ export class VotingEventComponent implements OnInit {
this.authenticationService.setMessage('Request not authorized - pls login and try again');
this.router.navigate(['login']);
}

getNextStepButtonText() {
const nextStepName = getNextActionName(this.getSelectedEvent());
if (!nextStepName) {
throw new Error(
`No next step for event ${this.getSelectedEvent().name} which is already at step ${getActionName(this.getSelectedEvent())}`
);
}
return `Go to "${nextStepName}"`;
}
isNextStepAvailable() {
return !!getNextAction(this.getSelectedEvent());
}

goToNextStep() {
this.backend.moveToNexFlowStep(this.getSelectedEvent()._id).subscribe(
() =>
(this.messageAction = `Event <strong> ${this.selectedName} </strong> moved to step "${getNextActionName(
this.getSelectedEvent()
)}"`),
(err) => {
if (err.errorCode === ERRORS.unauthorized) {
this.unauthorized();
return;
}
this.messageAction = `Event <strong> ${this.selectedName} </strong> could not be moved to next step -
look at the browser console to see if there is any detail`;
},
() => this.refreshVotingEvents()
);
}
}
114 changes: 114 additions & 0 deletions src/app/services/backend.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,120 @@ describe('BackendService', () => {
});
}, 100000);
});

describe('13 BackendService - vote and then move to the next step', () => {
it('13.1 add some votes and then move to the next step in the VotingEvent flow', (done) => {
const service: BackendService = TestBed.get(BackendService);
const votingEventName = 'a voting event to be moved to the next step';
let votes1;
let credentials1: VoteCredentials;
let votes2;
let credentials2: VoteCredentials;

let votingEvent;

let tech1: Technology;
let tech2: Technology;
const productionTag = 'Production';
const trainingTag = 'Training';

service
.authenticate(validUser.user, validUser.pwd)
.pipe(
tap((resp) => (testToken = resp)),
concatMap(() => service.getVotingEvents({ all: true })),
map((votingEvents) => {
const vEvents = votingEvents.filter((ve) => ve.name === votingEventName);
return vEvents.map((ve) => service.cancelVotingEvent(ve._id, true));
}),
concatMap((cancelVERequests) => (cancelVERequests.length > 0 ? forkJoin(cancelVERequests) : of(null)))
)
.pipe(
concatMap(() => service.createVotingEvent(votingEventName)),
concatMap(() => service.getVotingEvents()),
tap((votingEvents) => {
const vEvents = votingEvents.filter((ve) => ve.name === votingEventName);
expect(vEvents.length).toBe(1);
credentials1 = {
voterId: { firstName: 'fVoter1', lastName: 'lVoter1' },
votingEvent: null
};
credentials2 = {
voterId: { firstName: 'fVoter2', lastName: 'lVoter2' },
votingEvent: null
};
votingEvent = vEvents[0];
credentials1.votingEvent = votingEvent;
}),
concatMap(() => service.openVotingEvent(votingEvent._id)),
concatMap(() => service.getVotingEvent(votingEvent._id)),
// the first voter, Voter1, saves 2 votes,
// the second voter, Voter2, saves 2 votes,
// tech1 gets 2 "adopt" while tech2 gets 1 "hold" and 1 "trial"
// tech1 gets also 2 production tags and 1 training and tech2 gets 1 training tag
tap((vEvent) => {
tech1 = vEvent.technologies[0];
tech2 = vEvent.technologies[1];
votes1 = [
{ ring: 'adopt', technology: tech1, tags: [productionTag, trainingTag] },
{
ring: 'hold',
technology: tech2
}
];
votes2 = [
{ ring: 'adopt', technology: tech1, tags: [productionTag] },
{
ring: 'trial',
technology: tech2,
tags: [trainingTag]
}
];
credentials1.votingEvent = vEvent;
credentials2.votingEvent = vEvent;
}),
concatMap(() => service.saveVote(votes1, credentials1)),
concatMap(() => service.saveVote(votes2, credentials2)),
concatMap(() => service.moveToNexFlowStep(votingEvent._id)),
concatMap(() => service.getVotingEvent(votingEvent._id)),
tap((vEvent: VotingEvent) => {
const techs = vEvent.technologies;
const t1 = techs.find((t) => t.name === tech1.name);
const t2 = techs.find((t) => t.name === tech2.name);
expect(t1.votingResult).toBeDefined();
expect(t1.votingResult.votesForRing).toBeDefined();
expect(t1.votingResult.votesForRing.length).toBe(1);
expect(t1.votingResult.votesForRing[0].count).toBe(2);
expect(t1.votingResult.votesForRing[0].ring).toBe('adopt');
expect(t1.votingResult.votesForTag).toBeDefined();
expect(t1.votingResult.votesForTag.length).toBe(2);
const prodTagRes1 = t1.votingResult.votesForTag.find((t) => t.tag === productionTag);
const trainingTagRes1 = t1.votingResult.votesForTag.find((t) => t.tag === trainingTag);
expect(prodTagRes1.count).toBe(2);
expect(trainingTagRes1.count).toBe(1);

expect(t2.votingResult).toBeDefined();
expect(t2.votingResult.votesForRing).toBeDefined();
expect(t2.votingResult.votesForRing.length).toBe(2);
const holdRingRes2 = t2.votingResult.votesForRing.find((v) => v.ring === 'hold');
const trialRingRes2 = t2.votingResult.votesForRing.find((v) => v.ring === 'trial');
expect(holdRingRes2.count).toBe(1);
expect(trialRingRes2.count).toBe(1);
expect(t2.votingResult.votesForTag).toBeDefined();
expect(t2.votingResult.votesForTag.length).toBe(1);
const trainingTagRes2 = t2.votingResult.votesForTag.find((t) => t.tag === trainingTag);
expect(trainingTagRes2.count).toBe(1);
})
)
.subscribe({
error: (err) => {
console.error('13.1 test "add some votes and then move to the next step in the VotingEvent flow"', err);
throw new Error('"add some votes and then move to the next step in the VotingEvent flow" does not work');
},
complete: () => done()
});
}, 100000);
});
});

describe('redirect to radar page', () => {
Expand Down
11 changes: 11 additions & 0 deletions src/app/services/backend.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,4 +518,15 @@ export class BackendService {
const radarUrl = environment.radarURL + '?title=' + votingEvent.name + '&subtitle=' + subtitle + '&sheetId=' + backendUrlWithParams;
window.open(radarUrl, '_blank');
}

moveToNexFlowStep(id: string) {
const payload = this.buildPostPayloadForService(ServiceNames.moveToNexFlowStep);
payload['_id'] = id;
return this.http.post(this.url, payload).pipe(
map((resp: any) => {
return resp.data;
}),
catchError(this.handleError)
);
}
}
1 change: 1 addition & 0 deletions src/app/services/service-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export enum ServiceNames {
calculateBlipsFromAllEvents,
openForRevote,
closeForRevote,
moveToNexFlowStep,
getConfiguration,
authenticate,
authenticateForVotingEvent,
Expand Down
16 changes: 16 additions & 0 deletions src/app/utils/voting-event-flow.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ export function getActionName(votingEvent: VotingEvent) {
return getFlowStep(votingEvent).action.name;
}

export function getNextAction(votingEvent: VotingEvent) {
const nextFlowStep = getNextFlowStep(votingEvent);
return nextFlowStep ? nextFlowStep.action : null;
}
export function getNextActionName(votingEvent: VotingEvent) {
const nextAction = getNextAction(votingEvent);
return nextAction ? nextAction.name : null;
}

export function getActionRoute(votingEvent: VotingEvent) {
const actionName = getActionName(votingEvent);
let route: string;
Expand All @@ -51,6 +60,13 @@ function getFlowStep(votingEvent: VotingEvent) {
}
return votingEvent.flow.steps[round - 1];
}
function getNextFlowStep(votingEvent: VotingEvent) {
if (!votingEvent.flow) {
throw new Error(`Voting Event ${votingEvent.name} does not have a flow defined`);
}
const round = votingEvent.round ? votingEvent.round : 1;
return votingEvent.flow.steps.length > round ? votingEvent.flow.steps[round] : null;
}

// starting from a skinny VotingEvent, i.e. a VotingEvent which has just an id and its VotingEventFlow,
// reads all data of the VotingEvent inlcuding its technologies
Expand Down

0 comments on commit c0b6ec7

Please sign in to comment.