Skip to content

Commit

Permalink
Merge pull request #16 from Marvell-Consulting/feat/tasklist
Browse files Browse the repository at this point in the history
SW-88: Add initial version of the tasklist page
  • Loading branch information
wheelsandcogs authored Sep 26, 2024
2 parents f2db13a + b6a97a1 commit 7977c00
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 21 deletions.
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
})
.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

0 comments on commit 7977c00

Please sign in to comment.