Skip to content
Open
Show file tree
Hide file tree
Changes from all 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/results.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/results.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 ${String(totalPrototypes)} prototype${totalPrototypes === 1 ? '' : 's'}.`;
} else {
summaryText = `Showing ${String(countPrototypes)} prototype${countPrototypes === 1 ? '' : 's'} out of ${String(totalPrototypes)}.`;
}

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 ${String(additionalCount)} more previous version${pluralSuffix} (total ${String(totalCount)}).`
: '';

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,
},
sharedUsers,
showOwnerWarning: !isOwner,
viewerRows,
workspaces: workspaceOptions,
};
}
Loading
Loading