Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
"[markdown]": {
"editor.defaultFormatter": "DavidAnson.vscode-markdownlint"
},
"git.branchProtection": ["main"]
"git.branchProtection": ["main"],
"[nunjucks]": {
"editor.defaultFormatter": "okitavera.vscode-nunjucks-formatter"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add this to extensions.json please?

}
}
4 changes: 2 additions & 2 deletions src/routes/__test__/prototype-routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,7 @@ describe('renderResultsPage', () => {
await renderResultsPage(request, response);

expect(response.statusCode).toBe(200);
expect(response._getRenderView()).toBe('results.njk');
expect(response._getRenderView()).toBe('results/main.njk');
const data = response._getRenderData() as ResultsTemplatePayload;
expect(data.enableSuggestions).toBe(true);
expect(data.isOwner).toBe(true);
Expand All @@ -1320,7 +1320,7 @@ describe('renderResultsPage', () => {
await renderResultsPage(request, response);

expect(response.statusCode).toBe(200);
expect(response._getRenderView()).toBe('results.njk');
expect(response._getRenderView()).toBe('results/main.njk');
const data = response._getRenderData() as ResultsTemplatePayload;
expect(data.allUsers).toEqual([]);
expect(data.allWorkspaces).toHaveLength(1);
Expand Down
133 changes: 133 additions & 0 deletions src/routes/presenters/history-page.presenter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
export interface HistoryPageVM {
filterCreatedByItems: { selected: boolean; text: string; value: string }[];
filterOnlyCreatedItems: {
selected: boolean;
text: string;
value: string;
}[];

filterSharingItems: { selected: boolean; text: string; value: string }[];
hasPrototypes: boolean;
paginationItems?: object[];
paginationNextHref?: string;

paginationPreviousHref?: string;

perPageItems: { checked: boolean; text: string; value: string }[];
showPagination: boolean;
summaryText: string;
workspaceItems: { selected: boolean; text: string; value: string }[];
}

export function buildHistoryPageVM(input: {
countPrototypes: number;
createdBy: string;
onlyCreated: boolean;
paginationItems?: object[];
paginationNextHref?: string;
paginationPreviousHref?: string;
perPage: number;
sharing: string;
totalPrototypes: number;
workspaceItems: { selected: boolean; text: string; value: string }[];
}): HistoryPageVM {
const {
countPrototypes,
createdBy,
onlyCreated,
paginationItems,
paginationNextHref,
paginationPreviousHref,
perPage,
sharing,
totalPrototypes,
workspaceItems,
} = input;

// Build summary text
let summaryText = '';
if (totalPrototypes === 0) {
summaryText = 'There are no prototypes.';
} else if (totalPrototypes === countPrototypes) {
summaryText = `Showing all ${totalPrototypes} prototype${totalPrototypes !== 1 ? 's' : ''}.`;

Check warning on line 52 in src/routes/presenters/history-page.presenter.ts

View workflow job for this annotation

GitHub Actions / eslint

Invalid type "number" of template literal expression
} else {
summaryText = `Showing ${countPrototypes} prototype${countPrototypes !== 1 ? 's' : ''} out of ${totalPrototypes}.`;

Check warning on line 54 in src/routes/presenters/history-page.presenter.ts

View workflow job for this annotation

GitHub Actions / eslint

Invalid type "number" of template literal expression

Check warning on line 54 in src/routes/presenters/history-page.presenter.ts

View workflow job for this annotation

GitHub Actions / eslint

Invalid type "number" of template literal expression
}

return {
filterCreatedByItems: [
{
selected: createdBy === 'anyone',
text: 'Created by anyone',
value: 'anyone',
},
{
selected: createdBy === 'self',
text: 'Created by you',
value: 'self',
},
{
selected: createdBy === 'others',
text: 'Created by others',
value: 'others',
},
],
filterOnlyCreatedItems: [
{
selected: !onlyCreated,
text: 'All prototypes',
value: 'false',
},
{
selected: onlyCreated,
text: 'Exclude updates to existing prototypes',
value: 'true',
},
],

filterSharingItems: [
{
selected: sharing === 'all',
text: 'All prototypes',
value: 'all',
},
{
selected: sharing === 'public',
text: 'Prototypes shared publicly',
value: 'public',
},
{
selected: sharing === 'users',
text: 'Prototypes shared with specific users',
value: 'users',
},
{
selected: sharing === 'workspace',
text: 'Prototypes within a shared workspace',
value: 'workspace',
},
{
selected: sharing === 'private',
text: 'Prototypes that are not shared',
value: 'private',
},
],

hasPrototypes: totalPrototypes > 0,

paginationItems,

paginationNextHref,

paginationPreviousHref,

perPageItems: [10, 25, 50].map((n) => ({
checked: perPage === n,
text: String(n),
value: String(n),
})),
showPagination: !!paginationItems?.length,
summaryText,
workspaceItems,
};
}
76 changes: 76 additions & 0 deletions src/routes/presenters/results-page-history.presenter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import moment from 'moment';

import type { IPrototypeData } from '../../types/schemas/prototype-schema';
import type { IUser } from '../../types/schemas/user-schema';

export interface HistoryRowVM {
html: string;
}

export interface HistoryVM {
additionalCount: number;
additionalLabel: string;
hasMultiple: boolean;
pluralSuffix: string;
rows: HistoryRowVM[][];
totalCount: number;
}

export async function buildHistoryVM(
current: IPrototypeData,
previous: IPrototypeData[],
totalCount: number,
getUserById: (id: string) => Promise<IUser | null>,
viewingUserId: string
): Promise<HistoryVM> {
async function byAndWhen(userId: string, timestamp: Date): Promise<string> {
const creator =
userId === viewingUserId
? 'you'
: ((await getUserById(userId))?.name ?? 'an unknown user');

return `${creator.replace(/\s/g, '&nbsp;')} ${moment(timestamp)
.fromNow()
.replace(/\s/g, '&nbsp;')}`;
}

const rows: HistoryRowVM[][] = [
[
{
html: `${current.changesMade} by&nbsp;${await byAndWhen(
current.creatorUserId,
current.createdAt
)} (this&nbsp;version).`,
},
],
];

for (const pv of previous) {
rows.push([
{
html: `<a href="/prototype/${pv.id}">${pv.changesMade}</a> by&nbsp;${await byAndWhen(
pv.creatorUserId,
pv.createdAt
)}.`,
},
]);
}

const additionalCount = totalCount - previous.length;

const pluralSuffix = additionalCount === 1 ? '' : 's';

const additionalLabel =
additionalCount > 0
? `Plus ${additionalCount} more previous version${pluralSuffix} (total ${totalCount}).`

Check warning on line 65 in src/routes/presenters/results-page-history.presenter.ts

View workflow job for this annotation

GitHub Actions / eslint

Invalid type "number" of template literal expression

Check warning on line 65 in src/routes/presenters/results-page-history.presenter.ts

View workflow job for this annotation

GitHub Actions / eslint

Invalid type "number" of template literal expression
: '';

return {
additionalCount,
additionalLabel,
hasMultiple: rows.length > 1,
pluralSuffix,
rows,
totalCount,
};
}
51 changes: 51 additions & 0 deletions src/routes/presenters/results-page-overview.presenter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ITemplateData } from '../../types/schemas/prototype-schema';

export interface OverviewVM {
explanation?: string;
hasSuggestions: boolean;
noSuggestions: boolean;

promptType: 'json' | 'text';
showJsonEditor: boolean;
suggestions: SuggestionVM[];

switchPromptButtonText: string;
}

export interface SuggestionVM {
text: string;
visible: boolean;
}

export function buildOverviewVM(
json: ITemplateData,
generatedFrom: 'json' | 'text',
enableSuggestions: boolean
): OverviewVM {
const promptType = generatedFrom === 'json' ? 'json' : 'text';

const showJsonEditor = promptType === 'json';

const switchPromptButtonText =
promptType === 'json' ? 'Switch to text' : 'Switch to JSON';

const rawSuggestions = json.suggestions ?? [];
const suggestions: SuggestionVM[] = enableSuggestions
? rawSuggestions.slice(0, 3).map((s) => ({
text: s,
visible: true,
}))
: [];

return {
explanation: promptType === 'text' ? json.explanation : undefined,
hasSuggestions: suggestions.length > 0,
noSuggestions: suggestions.length === 0,

promptType,
showJsonEditor,
suggestions,

switchPromptButtonText,
};
}
74 changes: 74 additions & 0 deletions src/routes/presenters/results-page-sharing.presenter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { IPrototypeData } from '../../types/schemas/prototype-schema';
import type { IUser } from '../../types/schemas/user-schema';

export interface SharedUserRowVM {
nameAndEmail: string;
userId: string;
}

export interface SharingVM {
hasSharedUsers: boolean;
isOwner: boolean;
ownerRows: { colspan?: number; html?: string; text?: string }[][];
publicSharing: {
mode: 'none' | 'password' | 'public';
password: string;
};
sharedUsers: SharedUserRowVM[];
showOwnerWarning: boolean;
viewerRows: { text: string }[][];
workspaces: WorkspaceOptionVM[];
}

export interface WorkspaceOptionVM {
selected: boolean;
text: string;
value: string;
}

export function buildSharingVM(
prototype: IPrototypeData,
isOwner: boolean,
workspaceOptions: WorkspaceOptionVM[],
sharedWithUsers: IUser[]
): SharingVM {
let mode: 'none' | 'password' | 'public' = 'none';
if (prototype.livePrototypePublic) {
if (prototype.livePrototypePublicPassword) mode = 'password';
else mode = 'public';
}
const sharedUsers: SharedUserRowVM[] = sharedWithUsers.map((u) => ({
nameAndEmail: `${u.name} (${u.email})`,
userId: u.id,
}));
const hasSharedUsers = sharedUsers.length > 0;
const ownerRows: { colspan?: number; html?: string; text?: string }[][] =
[];
if (hasSharedUsers) {
for (const u of sharedUsers) {
ownerRows.push([
{ text: u.nameAndEmail },
{
html: `<button class="govuk-button govuk-button--warning govuk-!-margin-0 remove-shared-user-button" data-user-id="${u.userId}">Remove</button>`,
},
]);
}
} else {
ownerRows.push([{ colspan: 2, text: 'Not shared with any users' }]);
}
const viewerRows = sharedUsers.map((u) => [{ text: u.nameAndEmail }]);

return {
hasSharedUsers,
isOwner,
ownerRows,
publicSharing: {
mode,
password: prototype.livePrototypePublicPassword ?? '',

Check warning on line 67 in src/routes/presenters/results-page-sharing.presenter.ts

View workflow job for this annotation

GitHub Actions / eslint

Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined
},
sharedUsers,
showOwnerWarning: !isOwner,
viewerRows,
workspaces: workspaceOptions,
};
}
Loading
Loading