IO-992 Job Audit Logs

This commit is contained in:
Patrick Fic
2021-07-22 16:29:41 -07:00
parent 97aecd3ddc
commit 6b811d635b
48 changed files with 1263 additions and 961 deletions

View File

@@ -0,0 +1,46 @@
import { useQuery } from "@apollo/client";
import { Card, Table } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import { DateTimeFormatter } from "../../utils/DateFormatter";
export default function JobAuditTrail({ jobId }) {
const { t } = useTranslation();
const { loading, data } = useQuery(QUERY_AUDIT_TRAIL, {
variables: { jobid: jobId },
skip: !jobId,
});
const columns = [
{
title: t("audit.fields.created"),
dataIndex: "created",
key: "created",
render: (text, record) => (
<DateTimeFormatter>{record.created}</DateTimeFormatter>
),
},
{
title: t("audit.fields.useremail"),
dataIndex: "useremail",
key: "useremail",
},
{
title: t("audit.fields.operation"),
dataIndex: "operation",
key: "operation",
},
];
return (
<Card title={t("jobs.labels.audit")}>
<Table
loading={loading}
columns={columns}
rowKey="id"
dataSource={data ? data.audit_trail : []}
/>
</Card>
);
}

View File

@@ -6,18 +6,21 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JobsChangeStatus({ job, bodyshop, jobRO }) {
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
const { t } = useTranslation();
const [availableStatuses, setAvailableStatuses] = useState([]);
@@ -29,6 +32,10 @@ export function JobsChangeStatus({ job, bodyshop, jobRO }) {
})
.then((r) => {
notification["success"]({ message: t("jobs.successes.save") });
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobstatuschange(status),
});
// refetch();
})
.catch((error) => {

View File

@@ -1,18 +1,31 @@
import { gql } from "@apollo/client";
export const QUERY_AUDIT_TRAIL = gql`
query QUERY_AUDIT_TRAIL($id: uuid!) {
audit_trail(where: { recordid: { _eq: $id } }) {
query QUERY_AUDIT_TRAIL($jobid: uuid!) {
audit_trail(
where: { jobid: { _eq: $jobid } }
order_by: { created: desc }
) {
useremail
tabname
schemaname
recordid
jobid
operation
old_val
new_val
id
created
bodyshopid
}
}
`;
export const INSERT_AUDIT_TRAIL = gql`
mutation INSERT_AUDIT_TRAIL($auditObj: audit_trail_insert_input!) {
insert_audit_trail_one(object: $auditObj) {
id
jobid
billid
bodyshopid
created
operation
useremail
}
}
`;

View File

@@ -45,6 +45,7 @@ import ScheduleJobModalContainer from "../../components/schedule-job-modal/sched
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -279,6 +280,17 @@ export function JobsDetailPage({
>
<JobNotesContainer jobId={job.id} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<Icon component={FaRegStickyNote} />
{t("jobs.labels.audit")}
</span>
}
key="audit"
>
<JobAuditTrail jobId={job.id} />
</Tabs.TabPane>
</Tabs>
</Form>
</div>

View File

@@ -53,3 +53,8 @@ export const setOnline = (isOnline) => ({
type: ApplicationActionTypes.SET_ONLINE_STATUS,
payload: isOnline,
});
export const insertAuditTrail = ({ jobid, billid, operation }) => ({
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
payload: { jobid, billid, operation },
});

View File

@@ -1,6 +1,7 @@
import moment from "moment";
import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
import { INSERT_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import client from "../../utils/GraphQLClient";
import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
import {
@@ -125,6 +126,56 @@ export function* calculateScheduleLoad({ payload: end }) {
}
}
export function* applicationSagas() {
yield all([call(onCalculateScheduleLoad)]);
export function* onInsertAuditTrail() {
yield takeLatest(
ApplicationActionTypes.INSERT_AUDIT_TRAIL,
insertAuditTrailSaga
);
}
export function* insertAuditTrailSaga({
payload: { jobid, billid, operation },
}) {
const state = yield select();
const bodyshop = state.user.bodyshop;
const currentUser = state.user.currentUser;
console.log(
"Inserting audit trail for",
bodyshop.shopname,
currentUser.email,
jobid,
billid,
operation
);
const variables = {
auditObj: {
bodyshopid: bodyshop.id,
jobid,
billid,
operation,
useremail: currentUser.email,
},
};
yield client.mutate({
mutation: INSERT_AUDIT_TRAIL,
variables,
update(cache, { data }) {
cache.modify({
fields: {
audit_trail(existingAuditTrail, { readField }) {
const newAuditTrail = cache.writeQuery({
data: data.insert_audit_trail_one,
query: INSERT_AUDIT_TRAIL,
variables,
});
return [...existingAuditTrail, newAuditTrail];
},
},
});
},
});
}
export function* applicationSagas() {
yield all([call(onCalculateScheduleLoad), call(onInsertAuditTrail)]);
}

View File

@@ -10,5 +10,6 @@ const ApplicationActionTypes = {
SET_JOB_READONLY: "SET_JOB_READONLY",
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
};
export default ApplicationActionTypes;

View File

@@ -82,6 +82,11 @@
"values": "Values"
}
},
"audit_trail": {
"messages": {
"jobstatuschange": "Job status changed to {{status}}."
}
},
"billlines": {
"actions": {
"newline": "New Line"

View File

@@ -82,6 +82,11 @@
"values": ""
}
},
"audit_trail": {
"messages": {
"jobstatuschange": ""
}
},
"billlines": {
"actions": {
"newline": ""

View File

@@ -82,6 +82,11 @@
"values": ""
}
},
"audit_trail": {
"messages": {
"jobstatuschange": ""
}
},
"billlines": {
"actions": {
"newline": ""

View File

@@ -0,0 +1,8 @@
import i18n from "i18next";
const AuditTrailMapping = {
jobstatuschange: (status) =>
i18n.t("audit_trail.messages.jobstatuschange", { status }),
};
export default AuditTrailMapping;