Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SW-88: Add initial version of the tasklist page #16

Merged
merged 5 commits into from
Sep 26, 2024
Merged
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
1 change: 0 additions & 1 deletion src/middleware/ensure-authenticated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export const ensureAuthenticated: RequestHandler = (req: AuthedRequest, res, nex

// store the token string in the request as we need it for Authorization header in API requests
req.jwt = token;
logger.debug(`JWT: ${token}`);

// store the user object in the request for use in the frontend
req.user = decoded.user;
Expand Down
29 changes: 28 additions & 1 deletion src/middleware/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,32 @@
"IGNORE": "This column can be ignored",
"UNKNOWN": "Select"
}
},
"tasklist": {
"heading": "Dataset Overview",
"data": {
"subheading": "Data",
"datatable": "Data table"
},
"metadata": {
"subheading": "Metadata",
"title": "Title",
"summary": "Summary",
"data_collection": "Data collection",
"statistical_quality": "Statistical quality",
"data_sources": "Data sources",
"related_reports": "Related reports",
"update_frequency": "How often the dataset is updated",
"designation": "Designation",
"relevant_topics": "Relevant topics"
},
"publishing": {
"subheading": "Publishing",
"when": "When the dataset should be published",
"export": "Export text fields for translation",
"import": "Import translations",
"submit": "Submit for approval"
}
}
},
"view": {
Expand Down Expand Up @@ -153,7 +179,8 @@
"upload": {
"no_csv": "No CSV data provided",
"no_csv_data": "No CSV data available"
}
},
"not_found": "Page not found"
},
"api": {
"available": "API is available"
Expand Down
37 changes: 33 additions & 4 deletions src/routes/publish.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Blob } from 'buffer';
import { Blob } from 'node:buffer';

import { Response, Router } from 'express';
import multer from 'multer';
import { validate as validateUUID } from 'uuid';

import { logger } from '../utils/logger';
import { StatsWalesApi } from '../services/stats-wales-api';
Expand All @@ -14,6 +15,7 @@ import { ConfirmedImportDTO } from '../dtos2/confirmed-import-dto';
import { DimensionCreationDTO } from '../dtos2/dimension-creation-dto';
import { SourceType } from '../enums/source-type';
import { ViewError } from '../dtos2/view-error';
import { singleLangDataset } from '../utils/single-lang-dataset';

const t = i18next.t;
const storage = multer.memoryStorage();
Expand Down Expand Up @@ -289,6 +291,7 @@ publish.get('/preview', async (req: AuthedRequest, res: Response) => {
);
if (!previewData.success) {
logger.error('Failed to get preview data from the backend');
// eslint-disable-next-line require-atomic-updates
req.session.errors = generateViewErrors(undefined, 500, [
generateError('preview', 'errors.preview.failed_to_get_preview', {})
]);
Expand Down Expand Up @@ -316,6 +319,7 @@ async function confirmFileUpload(
currentFileImport.id
);
if (confirmedImport.success) {
// eslint-disable-next-line require-atomic-updates
req.session.currentImport = confirmedImport.fileImport;
req.session.save();
res.redirect(
Expand All @@ -326,6 +330,7 @@ async function confirmFileUpload(
logger.error(
`An HTTP error occurred trying to confirm import from the dataset with the following error: ${err}`
);
// eslint-disable-next-line require-atomic-updates
req.session.errors = generateViewErrors(currentDataset.id, 500, [
generateError('confirm', 'errors.preview.confirm_error', {})
]);
Expand Down Expand Up @@ -559,9 +564,12 @@ publish.post('/sources', upload.none(), async (req: AuthedRequest, res: Response
currentFileImport.id,
dimensionCreationRequest
);
res.status(200);
res.setHeader('Content-Type', 'application/json');
res.json(updatedDataset);

const datasetId = updatedDataset.dataset.id;

res.redirect(
`/${lang}/${req.i18n.t('routes.publish.start', { lng: lang })}/${datasetId}/${req.i18n.t('routes.publish.tasklist', { lng: lang })}`
);
} catch (err) {
logger.error(`Something went wrong with the Dimension Creation Request with the following error: ${err}`);
const errs = generateViewErrors(undefined, 500, [
Expand Down Expand Up @@ -606,3 +614,24 @@ publish.delete('/session/currentImport', (req: AuthedRequest, res: Response) =>
res.status(200);
res.json({ message: 'Current import has been deleted' });
});

publish.get('/:datasetId/tasklist', async (req: AuthedRequest, res: Response) => {
const lang = req.i18n.language;
const datasetId = req.params.datasetId as string;
const statsWalesApi = new StatsWalesApi(lang, req.jwt);

try {
if (!validateUUID(datasetId)) throw new Error('Invalid dataset ID');
const dataset = await statsWalesApi.getDataset(datasetId);
setCurrentToSession(dataset, req);

res.render('publish/tasklist', {
dataset: singleLangDataset(lang, dataset),
sourcesUrl: `/${lang}/${req.i18n.t('routes.publish.start', { lng: lang })}/${req.i18n.t('routes.publish.sources', { lng: lang })}`
});
} catch (err) {
logger.error(`Something went wrong viewing the tasklist: ${err}`);
res.status(404);
res.render('errors/not-found');
}
});
3 changes: 0 additions & 3 deletions src/routes/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import { validate as validateUUID } from 'uuid';
import { StatsWalesApi } from '../services/stats-wales-api';
import { FileList } from '../dtos2/filelist';
import { ViewErrDTO } from '../dtos2/view-dto';
import { i18next } from '../middleware/translation';
import { logger } from '../utils/logger';
import { AuthedRequest } from '../interfaces/authed-request';

const t = i18next.t;
export const view = Router();

const statsWalesApi = (req: AuthedRequest) => {
Expand All @@ -24,7 +22,6 @@ view.get('/', async (req: AuthedRequest, res: Response) => {
});

view.get('/:datasetId', async (req: AuthedRequest, res: Response) => {
const lang = req.i18n.language;
const page_number: number = Number.parseInt(req.query.page_number as string, 10) || 1;
const page_size: number = Number.parseInt(req.query.page_size as string, 10) || 100;

Expand Down
20 changes: 20 additions & 0 deletions src/services/stats-wales-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ export class StatsWalesApi {
return filelist;
}

public async getDataset(datasetId: string): Promise<DatasetDTO> {
logger.info(`Fetching dataset from ${this.backendUrl}/${this.lang}/dataset/${datasetId}`);
const dataset = await fetch(`${this.backendUrl}/${this.lang}/dataset/${datasetId}`, {
headers: this.authHeader
})
Dismissed Show dismissed Hide dismissed
.then((response) => {
if (response.ok) {
return response.json();
}
const err = new HttpError(response.status);
err.handleMessage(response.text());
throw err;
})
.then((api_res) => {
return api_res as DatasetDTO;
});

return dataset;
}

public async getDatasetView(datasetId: string, pageNumber: number, pageSize: number) {
logger.info(
`Fetching dataset view from ${this.backendUrl}/${this.lang}/dataset/${datasetId}/view?page_number=${pageNumber}&page_size=${pageSize}`
Expand Down
11 changes: 11 additions & 0 deletions src/utils/latest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { sortBy, last } from 'lodash';

import { DatasetDTO, ImportDTO, RevisionDTO } from '../dtos2/dataset-dto';

export const getLatestRevision = (dataset: DatasetDTO): RevisionDTO | undefined => {
return last(sortBy(dataset.revisions, 'revision_index'));
};

export const getLatestImport = (revision: RevisionDTO): ImportDTO | undefined => {
return last(sortBy(revision.imports, 'uploaded_at'));
};
14 changes: 14 additions & 0 deletions src/utils/single-lang-dataset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DatasetDTO } from '../dtos2/dataset-dto';

export const singleLangDataset = (lang: string, dataset: DatasetDTO) => {
return {
...dataset,
datasetInfo: dataset.datasetInfo?.find((info) => info.language === lang),
dimensions: dataset.dimensions?.map((dimension) => {
return {
...dimension,
dimensionInfo: dimension.dimensionInfo?.find((info) => info.language === lang)
};
})
};
};
10 changes: 10 additions & 0 deletions src/views/errors/not-found.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<%- include("../partials/header"); %>

<div class="govuk-width-container app-width-container">
<!-- <a href="#" class="govuk-back-link">Back</a> -->
<main class="govuk-main-wrapper" id="main-content" role="main">
<h1 class="govuk-heading-xl"><%- t('errors.not_found') %></h1>
</main>
</div>

<%- include("../partials/footer"); %>
147 changes: 147 additions & 0 deletions src/views/publish/tasklist.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<%- include("../partials/header"); %>

<div class="govuk-width-container">
<main class="govuk-main-wrapper " id="main-content" role="main">

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h2 class="region-subhead"><%= t('publish.tasklist.heading') %></h2>
<h1 class="govuk-heading-l" style="margin-bottom: 15px;"><%= dataset?.datasetInfo?.title %></h1>
</div>
</div>

<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h2 class="govuk-heading-m"><%= t('publish.tasklist.data.subheading') %></h2>
<ul class="govuk-task-list">
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="#">
<%= t('publish.tasklist.data.datatable') %>
</a>
</div>
</li>
<% dataset?.dimensions.forEach(function(dimension) { %>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="<%= sourcesUrl %>"><%=dimension?.dimensionInfo?.name %></a>
</div>
</li>
<% }); %>
</ul>

<h2 class="govuk-heading-m govuk-!-margin-top-5"><%= t('publish.tasklist.metadata.subheading') %></h2>
<ul class="govuk-task-list">
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="metadata-title.html" aria-describedby="prepare-application-3-status">
<%= t('publish.tasklist.metadata.title') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="metadata-summary.html" aria-describedby="prepare-application-3-status">
<%= t('publish.tasklist.metadata.summary') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="metadata-collection.html">
<%= t('publish.tasklist.metadata.data_collection') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="metadata-quality.html">
<%= t('publish.tasklist.metadata.statistical_quality') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="metadata-data-source-more" aria-describedby="prepare-application-4-status">
<%= t('publish.tasklist.metadata.data_sources') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="metadata-reports-more.html" aria-describedby="prepare-application-5-status">
<%= t('publish.tasklist.metadata.related_reports') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="metadata-frequency.html" aria-describedby="prepare-application-5-status">
<%= t('publish.tasklist.metadata.update_frequency') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="metadata-designation.html" aria-describedby="prepare-application-5-status">
<%= t('publish.tasklist.metadata.designation') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="metadata-topics.html" aria-describedby="prepare-application-5-status">
<%= t('publish.tasklist.metadata.relevant_topics') %>
</a>
</div>
</li>
</ul>

<h2 class="govuk-heading-m govuk-!-margin-top-5"><%= t('publish.tasklist.publishing.subheading') %></h2>
<ul class="govuk-task-list">
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="schedule" aria-describedby="prepare-application-5-status">
<%= t('publish.tasklist.publishing.when') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="schedule" aria-describedby="prepare-application-5-status">
<%= t('publish.tasklist.publishing.export') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="schedule" aria-describedby="prepare-application-5-status">
<%= t('publish.tasklist.publishing.import') %>
</a>
</div>
</li>
<li class="govuk-task-list__item govuk-task-list__item--with-link">
<div class="govuk-task-list__name-and-hint">
<a class="govuk-link govuk-task-list__link" href="schedule" aria-describedby="prepare-application-5-status">
<%= t('publish.tasklist.publishing.submit') %>
</a>
</div>
</li>
</ul>
</div>
</div>
</main>
</div>

<style>
.region-subhead {
color: #aa1111;
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
letter-spacing: 0.08em;
line-height: 20px;
text-transform: uppercase;
}
</style>

<%- include("../partials/footer"); %>
2 changes: 1 addition & 1 deletion test/helpers/mock-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const brokenPreviewDataset: DatasetDTO = {
]
}
]
}
};

export const completedDataset: DatasetDTO = {
id: '5caeb8ed-ea64-4a58-8cf0-b728308833e5',
Expand Down
Loading
Loading