IO-992 Job Audit Logs
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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)]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -82,6 +82,11 @@
|
||||
"values": "Values"
|
||||
}
|
||||
},
|
||||
"audit_trail": {
|
||||
"messages": {
|
||||
"jobstatuschange": "Job status changed to {{status}}."
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
"actions": {
|
||||
"newline": "New Line"
|
||||
|
||||
@@ -82,6 +82,11 @@
|
||||
"values": ""
|
||||
}
|
||||
},
|
||||
"audit_trail": {
|
||||
"messages": {
|
||||
"jobstatuschange": ""
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
"actions": {
|
||||
"newline": ""
|
||||
|
||||
@@ -82,6 +82,11 @@
|
||||
"values": ""
|
||||
}
|
||||
},
|
||||
"audit_trail": {
|
||||
"messages": {
|
||||
"jobstatuschange": ""
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
"actions": {
|
||||
"newline": ""
|
||||
|
||||
8
client/src/utils/AuditTrailMappings.js
Normal file
8
client/src/utils/AuditTrailMappings.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import i18n from "i18next";
|
||||
|
||||
const AuditTrailMapping = {
|
||||
jobstatuschange: (status) =>
|
||||
i18n.t("audit_trail.messages.jobstatuschange", { status }),
|
||||
};
|
||||
|
||||
export default AuditTrailMapping;
|
||||
Reference in New Issue
Block a user