From 58ef09f24c86e3e15821eeaa5c4afa7340627dd7 Mon Sep 17 00:00:00 2001 From: Audrey Hamelers <hamelers@ebi.ac.uk> Date: Mon, 28 Jun 2021 16:46:21 +0100 Subject: [PATCH] #1203 --- app/components/activity/operations.js | 4 +- app/components/manage-site/JobLogPage.jsx | 128 ++++++++++++++++++ .../manage-site/ManagementConsole.jsx | 6 +- app/components/manage-site/index.js | 1 + app/components/manage-site/operations.js | 12 ++ app/routes.jsx | 2 + .../xpub-model/entities/audit/data-access.js | 43 +++++- server/xpub-model/entities/audit/index.js | 3 +- .../entities/manuscript/data-access.js | 2 +- .../xpub-server/entities/audit/resolvers.js | 8 +- .../entities/audit/typeDefs.graphqls | 13 +- 11 files changed, 213 insertions(+), 9 deletions(-) create mode 100644 app/components/manage-site/JobLogPage.jsx diff --git a/app/components/activity/operations.js b/app/components/activity/operations.js index 161de846a..2e185f985 100644 --- a/app/components/activity/operations.js +++ b/app/components/activity/operations.js @@ -81,8 +81,8 @@ export const EXCEPTION_ALERT = gql` ` export const QUERY_ACTIVITY_INFO = gql` - query QueryActivitiesByManuscriptId($id: ID!) { - activities: epmc_queryActivitiesByManuscriptId(id: $id) { + query ManuscriptActivityLog($id: ID!) { + activities: manuscriptActivityLog(id: $id) { ...ManuscriptFragment deleted ebiState diff --git a/app/components/manage-site/JobLogPage.jsx b/app/components/manage-site/JobLogPage.jsx new file mode 100644 index 000000000..5c845ecf2 --- /dev/null +++ b/app/components/manage-site/JobLogPage.jsx @@ -0,0 +1,128 @@ +import React from 'react' +import { Query } from 'react-apollo' +import moment from 'moment' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { JOB_LOG } from './operations' +import { Loading, LoadingIcon, Table, Notification } from '../ui' +import ManagementBase from './ManagementBase' + +const DetailsTable = styled(Table)` + width: 100%; + @media screen and (max-width: 600px) { + th { + display: none; + } + tr { + border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')}; + } + } +` +const TD = styled.td` + font-size: ${th('fontSizeBaseSmall')}; + word-break: break-word; + @media screen and (max-width: 600px) { + display: inline-block; + width: 100%; + border: 0 !important; + } +` +const TdDate = styled(TD)` + vertical-align: top; + width: 1%; + white-space: nowrap; + @media screen and (max-width: 600px) { + width: 50%; + white-space: normal; + } +` + +const ParseUpdate = ({ col, val }) => { + switch (val) { + case null: + case '': + return `Removed previous ${col}.` + case 't': + return `Set job ${col}` + case 'f': + return `Set job not ${col}` + default: + if (moment(val).isValid()) + return `Job ${col} at: ${moment + .utc(val) + .local() + .format('DD/MM/YYYY HH:mm:ss')}` + return `Job ${col} is: ${val}` + } +} + +const EventList = ({ entry: { id, originalData, changes } }) => { + let rowData = changes + if (!changes) { + rowData = originalData + Object.keys(rowData).forEach(k => rowData[k] === null && delete rowData[k]) + } + const updateList = Object.keys(rowData) + return updateList.map(key => ( + <React.Fragment key={id + key}> + <ParseUpdate col={key.replace(/_/g, ' ')} val={rowData[key]} /> + <br /> + </React.Fragment> + )) +} + +const JobLog = ({ match, ...props }) => ( + <Query + fetchPolicy="no-cache" + query={JOB_LOG} + variables={{ name: match.params.name }} + > + {({ data, loading }) => { + if (loading) { + return ( + <Loading> + <LoadingIcon /> + </Loading> + ) + } + if (!data) { + return ( + <Notification type="error"> + Error loading job log from database + </Notification> + ) + } + const { jobLog } = data + return ( + <DetailsTable> + <tbody> + <tr> + <th>Date</th> + <th>Datacenter</th> + <th>Event</th> + </tr> + {jobLog.map(entry => ( + <tr key={entry.id}> + <TdDate> + {moment(entry.created).format('DD/MM/YYYY HH:mm')} + </TdDate> + <TdDate>{entry.dataCenter}</TdDate> + <TD> + <EventList entry={entry} /> + </TD> + </tr> + ))} + </tbody> + </DetailsTable> + ) + }} + </Query> +) + +const JobLogTitle = ManagementBase(JobLog) + +const JobLogPage = ({ ...props }) => ( + <JobLogTitle pageTitle={`Job log: ${props.match.params.name}`} {...props} /> +) + +export default JobLogPage diff --git a/app/components/manage-site/ManagementConsole.jsx b/app/components/manage-site/ManagementConsole.jsx index 9e99d92e0..b347683cb 100644 --- a/app/components/manage-site/ManagementConsole.jsx +++ b/app/components/manage-site/ManagementConsole.jsx @@ -3,7 +3,7 @@ import moment from 'moment' import { Query } from 'react-apollo' import styled, { withTheme } from 'styled-components' import { th } from '@pubsweet/ui-toolkit' -import { Icon, H2 } from '@pubsweet/ui' +import { Icon, H2, Link } from '@pubsweet/ui' import { B, Loading, LoadingIcon, Table, Notification } from '../ui' import ManagementBase from './ManagementBase' import ConfigurationForm from './ConfigurationForm' @@ -67,7 +67,9 @@ const Console = () => ( <td> {' '} <Desc> - <B>{job.name} </B> + <B> + <Link to={`/manage/job/${job.name}`}>{job.name}</Link>{' '} + </B> <span>{job.description}</span> </Desc> </td> diff --git a/app/components/manage-site/index.js b/app/components/manage-site/index.js index f3fe73df0..b4e42d4bf 100644 --- a/app/components/manage-site/index.js +++ b/app/components/manage-site/index.js @@ -1,3 +1,4 @@ export { default as UserSearch } from './UserSearch' export { default as ManagementConsole } from './ManagementConsole' export { default as MetricsPage } from './MetricsPage' +export { default as JobLogPage } from './JobLogPage' diff --git a/app/components/manage-site/operations.js b/app/components/manage-site/operations.js index 4b89573ef..d05fbe98e 100644 --- a/app/components/manage-site/operations.js +++ b/app/components/manage-site/operations.js @@ -13,6 +13,18 @@ export const JOB_LIST = gql` } ` +export const JOB_LOG = gql` + query JobLog($name: String!) { + jobLog(name: $name) { + id + created + dataCenter + originalData + changes + } + } +` + export const PROPS = gql` query ListProps { getProps { diff --git a/app/routes.jsx b/app/routes.jsx index fc1883ee9..9b690bc3d 100755 --- a/app/routes.jsx +++ b/app/routes.jsx @@ -10,6 +10,7 @@ import { ManagementConsole, UserSearch, MetricsPage, + JobLogPage, } from './components/manage-site' import ManageAccount from './components/manage-account/ManageAccount' import PasswordResetEmail from './components/password-reset/PasswordResetEmailContainer' @@ -56,6 +57,7 @@ const Routes = () => ( <PrivateRoute component={ManagementConsole} exact path="/manage" /> <PrivateRoute component={MetricsPage} exact path="/manage/metrics" /> <PrivateRoute component={UserSearch} exact path="/manage/users" /> + <PrivateRoute component={JobLogPage} exact path="/manage/job/:name" /> <PrivateRoute component={ManageAccount} exact diff --git a/server/xpub-model/entities/audit/data-access.js b/server/xpub-model/entities/audit/data-access.js index daa1d1e49..b50c4b347 100644 --- a/server/xpub-model/entities/audit/data-access.js +++ b/server/xpub-model/entities/audit/data-access.js @@ -240,4 +240,45 @@ order by top.wk desc } } -module.exports = Audit +class JobAudit extends EpmcBaseModel { + static get tableName() { + return 'audit.job_log' + } + + static get schema() { + return { + properties: { + id: { type: 'uuid' }, + created: { type: 'timestamp' }, + jobName: { type: 'string' }, + dataCenter: { type: 'string' }, + action: { type: 'string' }, + originalData: { type: 'object' }, + changes: { type: 'object' }, + }, + } + } + + static get relationMappings() { + const Job = require('../config/data-access') + return { + job: { + relation: Model.HasOneRelation, + modelClass: Job, + join: { + from: 'audit.job_log.job_name', + to: 'config.job.name', + }, + }, + } + } + + static async jobLog(name) { + return JobAudit.query() + .where('jobName', name) + .orderBy('created', 'desc') + .limit(100) + } +} + +module.exports = { Audit, JobAudit } diff --git a/server/xpub-model/entities/audit/index.js b/server/xpub-model/entities/audit/index.js index 410471bf1..338bcc153 100644 --- a/server/xpub-model/entities/audit/index.js +++ b/server/xpub-model/entities/audit/index.js @@ -1,4 +1,4 @@ -const Audit = require('./data-access') +const { Audit, JobAudit } = require('./data-access') const AuditManager = { modelName: 'Audit', @@ -7,6 +7,7 @@ const AuditManager = { getMetrics: Audit.getMetrics, weeklyMetrics: Audit.weeklyMetrics, publisherMetrics: Audit.publisherMetrics, + jobLog: JobAudit.jobLog, } module.exports = AuditManager diff --git a/server/xpub-model/entities/manuscript/data-access.js b/server/xpub-model/entities/manuscript/data-access.js index e2717c2ec..2fd81ef9a 100644 --- a/server/xpub-model/entities/manuscript/data-access.js +++ b/server/xpub-model/entities/manuscript/data-access.js @@ -159,7 +159,7 @@ class Manuscript extends EpmcBaseModel { const Review = require('../review/data-access') const Team = require('../team/data-access') const User = require('../user/data-access') - const Audit = require('../audit/data-access') + const { Audit } = require('../audit/data-access') return { reviews: { relation: Model.HasManyRelation, diff --git a/server/xpub-server/entities/audit/resolvers.js b/server/xpub-server/entities/audit/resolvers.js index 10bfa147b..a541e3130 100644 --- a/server/xpub-server/entities/audit/resolvers.js +++ b/server/xpub-server/entities/audit/resolvers.js @@ -6,7 +6,7 @@ const { ManuscriptManager, AuditManager, OrganizationManager } = rfr( const resolvers = { Query: { - async epmc_queryActivitiesByManuscriptId(_, { id }, { user }) { + async manuscriptActivityLog(_, { id }, { user }) { if (!user) { throw new Error('You are not authenticated!') } @@ -35,6 +35,12 @@ const resolvers = { const orgId = await OrganizationManager.getOrganizationID(preprint) return AuditManager.weeklyMetrics(orgId) }, + async jobLog(_, { name }, { user }) { + if (!user) { + throw new Error('You are not authenticated!') + } + return AuditManager.jobLog(name) + }, }, } diff --git a/server/xpub-server/entities/audit/typeDefs.graphqls b/server/xpub-server/entities/audit/typeDefs.graphqls index 64e41b75d..b2da30b16 100644 --- a/server/xpub-server/entities/audit/typeDefs.graphqls +++ b/server/xpub-server/entities/audit/typeDefs.graphqls @@ -18,10 +18,21 @@ type Audit implements Object { } extend type Query { - epmc_queryActivitiesByManuscriptId(id: ID!): Manuscript + manuscriptActivityLog(id: ID!): Manuscript getMetrics(startMonth: Int, endMonth: Int, preprint: Boolean): [Metrics] weeklyMetrics(preprint: Boolean): [Metrics] publisherMetrics(startMonth: Int, endMonth: Int): [Stats] + jobLog(name: String!): [JobAudit] +} + +type JobAudit { + id: ID! + created: DateTime! + action: String + dataCenter: String + originalData: JSON + changes: JSON + jobName: String } type Metrics { -- GitLab