From 6f42f283b357623d1bd791fc8b4b2d780be7f357 Mon Sep 17 00:00:00 2001 From: Christopher Sunkel Date: Mon, 26 Feb 2024 17:59:42 +0000 Subject: [PATCH] Migrate company edit history page to React Router (#6548) * Refactor company edit history page to use React Router * Update and rewrite tests * Remove legacy code --- .../edit-history/__test__/controller.test.js | 76 ----- .../apps/edit-history/__test__/router.test.js | 25 -- .../__test__/transformers.test.js | 263 ---------------- .../client/CompanyEditHistory.jsx | 80 ----- .../companies/apps/edit-history/controller.js | 38 --- .../companies/apps/edit-history/router.js | 8 - .../apps/edit-history/transformers.js | 52 ---- .../edit-history/views/client-container.njk | 8 - src/apps/companies/repos.js | 10 - src/apps/companies/router.js | 2 - src/apps/routers.js | 1 + .../CollectionList/CollectionItem.jsx | 3 +- src/client/components/Metadata/index.jsx | 4 +- .../Resource/CompanyAuditHistory.js | 6 + src/client/components/Resource/Resource.jsx | 5 +- src/client/components/Resource/index.jsx | 1 + src/client/components/Resource/tasks.js | 2 + src/client/index.jsx | 6 - .../CompanyEditHistory/CompanyEditHistory.jsx | 66 ++++ .../CompanyEditHistory}/constants.js | 44 ++- .../CompanyEditHistory/transformers.js | 121 ++++++++ src/client/routes.js | 6 + .../cypress/specs/DIT/audit-spec.js | 20 +- .../specs/companies/edit-history-spec.js | 282 ++++++------------ .../support/collection-list-assertions.js | 4 +- .../v4/company-audit/company-audit.json | 25 +- test/selectors/company/edit.js | 1 - 27 files changed, 358 insertions(+), 801 deletions(-) delete mode 100644 src/apps/companies/apps/edit-history/__test__/controller.test.js delete mode 100644 src/apps/companies/apps/edit-history/__test__/router.test.js delete mode 100644 src/apps/companies/apps/edit-history/__test__/transformers.test.js delete mode 100644 src/apps/companies/apps/edit-history/client/CompanyEditHistory.jsx delete mode 100644 src/apps/companies/apps/edit-history/controller.js delete mode 100644 src/apps/companies/apps/edit-history/router.js delete mode 100644 src/apps/companies/apps/edit-history/transformers.js delete mode 100644 src/apps/companies/apps/edit-history/views/client-container.njk create mode 100644 src/client/components/Resource/CompanyAuditHistory.js create mode 100644 src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/CompanyEditHistory.jsx rename src/{apps/companies/apps/edit-history => client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory}/constants.js (53%) create mode 100644 src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/transformers.js diff --git a/src/apps/companies/apps/edit-history/__test__/controller.test.js b/src/apps/companies/apps/edit-history/__test__/controller.test.js deleted file mode 100644 index c326c223179..00000000000 --- a/src/apps/companies/apps/edit-history/__test__/controller.test.js +++ /dev/null @@ -1,76 +0,0 @@ -const proxyquire = require('proxyquire') - -const buildMiddlewareParameters = require('../../../../../../test/unit/helpers/middleware-parameters-builder') -const companyMock = require('../../../../../../test/unit/data/companies/company-v4.json') -const urls = require('../../../../../../src/lib/urls') - -describe('rendering Edit History', () => { - const getCompanyAuditLogStub = sinon.stub() - const transformCompanyAuditLogStub = sinon.stub() - const controller = proxyquire('../controller', { - '../../repos': { - getCompanyAuditLog: getCompanyAuditLogStub, - }, - './transformers': { - transformCompanyAuditLog: transformCompanyAuditLogStub, - }, - }) - - context('when "Edit History" renders successfully', () => { - const middlewareParams = buildMiddlewareParameters({ - company: companyMock, - }) - - getCompanyAuditLogStub.resolves({ - count: 1, - next: null, - previous: null, - results: [], - }) - - transformCompanyAuditLogStub.returns([]) - - before(async () => { - await controller.renderEditHistory( - middlewareParams.reqMock, - middlewareParams.resMock, - middlewareParams.nextSpy - ) - }) - - it('should render the edit history template', () => { - const expectedTemplate = - 'companies/apps/edit-history/views/client-container' - expect(middlewareParams.resMock.render).to.be.calledOnceWithExactly( - expectedTemplate, - { - props: { - dataEndpoint: urls.companies.editHistory.data( - 'a73efeba-8499-11e6-ae22-56b6b6499611' - ), - }, - } - ) - }) - - it('should add 3 breadcrumbs', () => { - expect(middlewareParams.resMock.breadcrumb.args).to.deep.equal([ - [ - 'Mercury Ltd', - urls.companies.detail('a73efeba-8499-11e6-ae22-56b6b6499611'), - ], - [ - 'Business details', - urls.companies.businessDetails( - 'a73efeba-8499-11e6-ae22-56b6b6499611' - ), - ], - ['Edit History'], - ]) - }) - - it('should not call next() with an error', () => { - expect(middlewareParams.nextSpy).to.not.have.been.called - }) - }) -}) diff --git a/src/apps/companies/apps/edit-history/__test__/router.test.js b/src/apps/companies/apps/edit-history/__test__/router.test.js deleted file mode 100644 index 749cfd3fa98..00000000000 --- a/src/apps/companies/apps/edit-history/__test__/router.test.js +++ /dev/null @@ -1,25 +0,0 @@ -const router = require('../router') - -describe('Edit history route', () => { - it('should define both "Edit history" and "data" routes', () => { - const paths = router.stack - .filter((r) => r.route) - .map((r) => { - return { - path: r.route.path, - methods: Object.keys(r.route.methods).map((method) => method), - } - }) - - expect(paths).to.deep.equal([ - { - path: '/', - methods: ['get'], - }, - { - path: '/data', - methods: ['get'], - }, - ]) - }) -}) diff --git a/src/apps/companies/apps/edit-history/__test__/transformers.test.js b/src/apps/companies/apps/edit-history/__test__/transformers.test.js deleted file mode 100644 index c771438bf64..00000000000 --- a/src/apps/companies/apps/edit-history/__test__/transformers.test.js +++ /dev/null @@ -1,263 +0,0 @@ -const { transformCompanyAuditLog } = require('../transformers') - -describe('Edit history transformers', () => { - it('should return an empty array when there are no changes', () => { - const transformed = transformCompanyAuditLog([]) - expect(transformed).to.deep.equal([]) - }) - - it('should transform an archived company', () => { - expect( - transformCompanyAuditLog([ - { - id: 236, - user: { - id: '8b76daf2-0f8a-4887-9b4b-43bbda21c934', - first_name: 'Paul', - last_name: 'Gain', - name: 'Paul Gain', - email: 'paul.gain@digital.trade.gov.uk', - }, - timestamp: '2019-12-10T18:07:27.864590Z', - comment: '', - changes: { - archived: [false, true], - archived_on: [null, '2019-12-10T18:07:27.974000Z'], - archived_reason: [null, 'Company is dissolved'], - archived_by: [null, 'Paul Gain'], - }, - }, - ]) - ).to.deep.equal([ - { - timestamp: '2019-12-10T18:07:27.864590Z', - changedBy: 'Paul Gain', - changes: [ - { - fieldName: 'Archived', - oldValue: false, - newValue: true, - }, - { - fieldName: 'Archived reason', - oldValue: null, - newValue: 'Company is dissolved', - }, - ], - }, - ]) - }) - - it('should transform an unarchived company', () => { - expect( - transformCompanyAuditLog([ - { - id: 237, - user: { - id: '8b76daf2-0f8a-4887-9b4b-43bbda21c934', - first_name: 'Paul', - last_name: 'Gain', - name: 'Paul Gain', - email: 'paul.gain@digital.trade.gov.uk', - }, - timestamp: '2019-12-10T18:12:37.649476Z', - comment: '', - changes: { - archived: [true, false], - archived_on: ['2019-12-10T18:07:27.974000Z', null], - archived_reason: ['Company is dissolved', ''], - archived_by: ['Paul Gain', null], - }, - }, - ]) - ).to.deep.equal([ - { - timestamp: '2019-12-10T18:12:37.649476Z', - changedBy: 'Paul Gain', - changes: [ - { - fieldName: 'Archived', - oldValue: true, - newValue: false, - }, - { - fieldName: 'Archived reason', - oldValue: 'Company is dissolved', - newValue: '', - }, - ], - }, - ]) - }) - - it('should transform a company address', () => { - expect( - transformCompanyAuditLog([ - { - id: 233, - user: { - id: '8b76daf2-0f8a-4887-9b4b-43bbda21c934', - first_name: 'Paul', - last_name: 'Gain', - name: 'Paul Gain', - email: 'paul.gain@digital.trade.gov.uk', - }, - timestamp: '2019-12-10T17:58:59.393078Z', - comment: '', - changes: { - address_1: [ - '14 Wharf Road', - 'Her Majesty’s Courts and Tribunals Service - 102 Petty France', - ], - address_2: ['', 'Westminster'], - address_town: ['Brentwood', 'London'], - address_county: ['Essex', 'Greater London'], - address_postcode: ['CM14 4LQ', 'SW1H 9AJ'], - }, - }, - ]) - ).to.deep.equal([ - { - timestamp: '2019-12-10T17:58:59.393078Z', - changedBy: 'Paul Gain', - changes: [ - { - fieldName: 'Address line 1', - oldValue: '14 Wharf Road', - newValue: - 'Her Majesty’s Courts and Tribunals Service - 102 Petty France', - }, - { - fieldName: 'Address line 2 (optional)', - oldValue: '', - newValue: 'Westminster', - }, - { - fieldName: 'Address town', - oldValue: 'Brentwood', - newValue: 'London', - }, - { - fieldName: 'Address county', - oldValue: 'Essex', - newValue: 'Greater London', - }, - { - fieldName: 'Address postcode', - oldValue: 'CM14 4LQ', - newValue: 'SW1H 9AJ', - }, - ], - }, - ]) - }) - - it('should transform a DnB company', () => { - expect( - transformCompanyAuditLog([ - { - id: 235, - user: { - id: '8b76daf2-0f8a-4887-9b4b-43bbda21c934', - first_name: 'Paul', - last_name: 'Gain', - name: 'Paul Gain', - email: 'paul.gain@digital.trade.gov.uk', - }, - timestamp: '2019-12-10T18:04:54.287410Z', - comment: '', - changes: { - sector: ['Biotechnology and Pharmaceuticals', 'Airports'], - description: [null, 'Superior editing services'], - uk_region: ['South East', 'London'], - }, - }, - ]) - ).to.deep.equal([ - { - timestamp: '2019-12-10T18:04:54.287410Z', - changedBy: 'Paul Gain', - changes: [ - { - fieldName: 'DBT sector', - oldValue: 'Biotechnology and Pharmaceuticals', - newValue: 'Airports', - }, - { - fieldName: 'Business description (optional)', - oldValue: null, - newValue: 'Superior editing services', - }, - { - fieldName: 'DBT region', - oldValue: 'South East', - newValue: 'London', - }, - ], - }, - ]) - }) - - it('should transform a company trading address', () => { - expect( - transformCompanyAuditLog([ - { - id: 234, - user: { - id: '8b76daf2-0f8a-4887-9b4b-43bbda21c934', - first_name: 'Paul', - last_name: 'Gain', - name: 'Paul Gain', - email: 'paul.gain@digital.trade.gov.uk', - }, - timestamp: '2019-12-10T18:00:52.265115Z', - comment: '', - changes: { - trading_names: [[], ['Edit History Enterprises']], - }, - }, - ]) - ).to.deep.equal([ - { - timestamp: '2019-12-10T18:00:52.265115Z', - changedBy: 'Paul Gain', - changes: [ - { - fieldName: 'Trading name(s)', - oldValue: null, - newValue: 'Edit History Enterprises', - }, - ], - }, - ]) - }) - - it('should transform when the user is null - DnB update', () => { - expect( - transformCompanyAuditLog([ - { - id: 234, - user: null, - timestamp: '2019-12-10T14:39:28.768359Z', - comment: 'Updated from D&B [rq:company_update]', - changes: { - address_1: ['1600 Amphitheatre Pkwyz', '1600 Amphitheatre Pkwy'], - dnb_modified_on: [null, '2019-12-10T14:39:28.768000Z'], - }, - }, - ]) - ).to.deep.equal([ - { - timestamp: '2019-12-10T14:39:28.768359Z', - changedBy: 'automaticUpdate', - changes: [ - { - fieldName: 'Address line 1', - oldValue: '1600 Amphitheatre Pkwyz', - newValue: '1600 Amphitheatre Pkwy', - }, - ], - }, - ]) - }) -}) diff --git a/src/apps/companies/apps/edit-history/client/CompanyEditHistory.jsx b/src/apps/companies/apps/edit-history/client/CompanyEditHistory.jsx deleted file mode 100644 index bbf0102df25..00000000000 --- a/src/apps/companies/apps/edit-history/client/CompanyEditHistory.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { isBoolean, isNumber } from 'lodash' - -import EditHistory from '../../../../../client/components/EditHistory/EditHistory' -import { currencyGBP } from '../../../../../client/utils/number-utils' - -import { - ARCHIVED, - NOT_ARCHIVED, - YES, - NO, - AUTOMATIC_UPDATE, - CHANGE_TYPE_TEXT, - NOT_SET, -} from '../constants' - -const { - formatMediumDateTime, - isDateValid, -} = require('../../../../../client/utils/date') - -const CURRENCY_FIELDS = ['Turnover'] - -function getValueFromBoolean(value, field) { - if ( - field === 'Is number of employees estimated' || - field === 'Is turnover estimated' - ) { - return value ? YES : NO - } - - if (field === 'Archived') { - return value ? ARCHIVED : NOT_ARCHIVED - } -} - -function getValue(value, field) { - if (isBoolean(value)) { - return getValueFromBoolean(value, field) - } - - if (isNumber(value)) { - return CURRENCY_FIELDS.includes(field) - ? currencyGBP(value, { - maximumSignificantDigits: 2, - }) - : value.toString() - } - - if (isDateValid(value)) { - return formatMediumDateTime(value) - } - - return value || NOT_SET -} - -function getUpdatedBy(timestamp, changedBy) { - const formattedTime = formatMediumDateTime(timestamp) - return changedBy === AUTOMATIC_UPDATE - ? `Automatically updated on ${formattedTime}` - : `Updated on ${formattedTime} by ${changedBy}` -} - -function CompanyEditHistory({ dataEndpoint }) { - return ( - - ) -} - -CompanyEditHistory.propTypes = { - dataEndpoint: PropTypes.string.isRequired, -} - -export default CompanyEditHistory diff --git a/src/apps/companies/apps/edit-history/controller.js b/src/apps/companies/apps/edit-history/controller.js deleted file mode 100644 index 210d4fd6948..00000000000 --- a/src/apps/companies/apps/edit-history/controller.js +++ /dev/null @@ -1,38 +0,0 @@ -const { companies } = require('../../../../lib/urls') -const { getCompanyAuditLog } = require('../../repos') -const { transformCompanyAuditLog } = require('./transformers') - -function renderEditHistory(req, res) { - const { company } = res.locals - - res - .breadcrumb(company.name, companies.detail(company.id)) - .breadcrumb('Business details', companies.businessDetails(company.id)) - .breadcrumb('Edit History') - .render('companies/apps/edit-history/views/client-container', { - props: { - dataEndpoint: companies.editHistory.data(company.id), - }, - }) -} - -async function fetchCompanyAuditLog(req, res, next) { - try { - const { company } = res.locals - const { page } = req.query - - const { results, count } = await getCompanyAuditLog(req, company.id, page) - - res.json({ - results: transformCompanyAuditLog(results), - count, - }) - } catch (error) { - next(error) - } -} - -module.exports = { - renderEditHistory, - fetchCompanyAuditLog, -} diff --git a/src/apps/companies/apps/edit-history/router.js b/src/apps/companies/apps/edit-history/router.js deleted file mode 100644 index 4d5a2b802eb..00000000000 --- a/src/apps/companies/apps/edit-history/router.js +++ /dev/null @@ -1,8 +0,0 @@ -const router = require('express').Router() - -const { renderEditHistory, fetchCompanyAuditLog } = require('./controller') - -router.get('/', renderEditHistory) -router.get('/data', fetchCompanyAuditLog) - -module.exports = router diff --git a/src/apps/companies/apps/edit-history/transformers.js b/src/apps/companies/apps/edit-history/transformers.js deleted file mode 100644 index 0a5b688fbea..00000000000 --- a/src/apps/companies/apps/edit-history/transformers.js +++ /dev/null @@ -1,52 +0,0 @@ -const { isEmpty, capitalize, lowerCase } = require('lodash') - -const { AUTOMATIC_UPDATE } = require('./constants') - -const { - EXCLUDED_FIELDS, - COMPANY_FIELD_NAME_TO_LABEL_MAP, -} = require('./constants') - -function mapFieldNameToLabel(fieldName) { - return ( - COMPANY_FIELD_NAME_TO_LABEL_MAP[fieldName] || - capitalize(lowerCase(fieldName)) - ) -} - -const unwrapFromArray = (change) => { - if (Array.isArray(change)) { - return change.length > 1 ? change : change[0] || null - } - return change -} - -const transformChanges = (changes) => { - return Object.keys(changes) - .filter((fieldName) => !EXCLUDED_FIELDS.includes(fieldName)) - .map((fieldName) => ({ - fieldName: mapFieldNameToLabel(fieldName), - oldValue: unwrapFromArray(changes[fieldName][0]), - newValue: unwrapFromArray(changes[fieldName][1]), - })) -} - -const transformCompanyAuditLogItem = ({ timestamp, user, changes }) => { - const changedBy = user - ? isEmpty(user.name) - ? user.email - : user.name - : AUTOMATIC_UPDATE - - return { - timestamp, - changedBy, - changes: transformChanges(changes), - } -} - -const transformCompanyAuditLog = (auditLog) => { - return auditLog.map(transformCompanyAuditLogItem) -} - -module.exports = { transformCompanyAuditLog } diff --git a/src/apps/companies/apps/edit-history/views/client-container.njk b/src/apps/companies/apps/edit-history/views/client-container.njk deleted file mode 100644 index 40b0328a960..00000000000 --- a/src/apps/companies/apps/edit-history/views/client-container.njk +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "_layouts/template.njk" %} -{% block body_main_content %} - {% component 'react-slot', { - id: 'company-edit-history', - props: props - } %} - -{% endblock %} diff --git a/src/apps/companies/repos.js b/src/apps/companies/repos.js index 128f87a274a..bd71c7e91cd 100644 --- a/src/apps/companies/repos.js +++ b/src/apps/companies/repos.js @@ -65,15 +65,6 @@ function updateCompany(req, companyId, body) { }) } -function getCompanyAuditLog(req, companyId, page = 1) { - const limit = 10 - const offset = limit * (page - 1) - return authorisedRequest(req, { - url: `${config.apiRoot}/v4/company/${companyId}/audit`, - qs: { limit, offset }, - }) -} - function getCompanySubsidiaries(req, companyId, page = 1) { const limit = 10 const offset = limit * (page - 1) @@ -164,7 +155,6 @@ module.exports = { archiveCompany, unarchiveCompany, updateCompany, - getCompanyAuditLog, getCompanySubsidiaries, getGlobalUltimateHierarchy, getOneListGroupCoreTeam, diff --git a/src/apps/companies/router.js b/src/apps/companies/router.js index cd3e51a17c8..c52709abe11 100644 --- a/src/apps/companies/router.js +++ b/src/apps/companies/router.js @@ -34,7 +34,6 @@ const addCompanyFormRouter = require('./apps/add-company/router') const editCompanyFormRouter = require('./apps/edit-company/router') const activityFeedRouter = require('./apps/activity-feed/router') const dnbHierarchyRouter = require('./apps/dnb-hierarchy/router') -const editHistoryRouter = require('./apps/edit-history/router') const matchCompanyRouter = require('./apps/match-company/router') const interactionsRouter = require('../interactions/router.sub-app') const companyListsRouter = require('../company-lists/router') @@ -63,7 +62,6 @@ router.get( router.use(urls.companies.create.route, addCompanyFormRouter) router.use(urls.companies.lists.index.route, companyListsRouter) router.use(urls.companies.edit.route, editCompanyFormRouter) -router.use(urls.companies.editHistory.index.route, editHistoryRouter) router.post(urls.companies.archive.route, archiveCompany) router.get(urls.companies.unarchive.route, unarchiveCompany) diff --git a/src/apps/routers.js b/src/apps/routers.js index 507eeda9bc5..cc6573ca9c1 100644 --- a/src/apps/routers.js +++ b/src/apps/routers.js @@ -129,6 +129,7 @@ const reactRoutes = [ '/companies/:companyId/exports/history/:countryId', '/omis/:orderId/quote', '/omis/reconciliation', + '/companies/:companyId/edit-history', ] reactRoutes.forEach((path) => { diff --git a/src/client/components/CollectionList/CollectionItem.jsx b/src/client/components/CollectionList/CollectionItem.jsx index 0bb8028ca5a..d72be64cb94 100644 --- a/src/client/components/CollectionList/CollectionItem.jsx +++ b/src/client/components/CollectionList/CollectionItem.jsx @@ -166,7 +166,8 @@ CollectionItem.propTypes = { ), metadata: PropTypes.arrayOf( PropTypes.shape({ - label: PropTypes.string.isRequired, + label: PropTypes.string, + key: PropTypes.string, value: PropTypes.node.isRequired, }) ), diff --git a/src/client/components/Metadata/index.jsx b/src/client/components/Metadata/index.jsx index 66742a00156..c32b8a0b19c 100644 --- a/src/client/components/Metadata/index.jsx +++ b/src/client/components/Metadata/index.jsx @@ -18,8 +18,8 @@ const StyledMetadataWrapper = styled('div')` const Metadata = ({ rows }) => rows && ( - {rows.map(({ label, value }) => ( - + {rows.map(({ label, value, key }) => ( + {value} ))} diff --git a/src/client/components/Resource/CompanyAuditHistory.js b/src/client/components/Resource/CompanyAuditHistory.js new file mode 100644 index 00000000000..514bb415690 --- /dev/null +++ b/src/client/components/Resource/CompanyAuditHistory.js @@ -0,0 +1,6 @@ +import { createCollectionResource } from './Resource' + +export default createCollectionResource( + 'changes to company business details', + (id) => id +) diff --git a/src/client/components/Resource/Resource.jsx b/src/client/components/Resource/Resource.jsx index 794c7959191..60d88ae8c83 100644 --- a/src/client/components/Resource/Resource.jsx +++ b/src/client/components/Resource/Resource.jsx @@ -212,7 +212,10 @@ export const createEntityResource = (name, endpoint) => { * */ export const createCollectionResource = (name, endpoint) => { - const EntityResource = createEntityResource(name, () => endpoint) + const EntityResource = + typeof endpoint === 'function' + ? createEntityResource(name, (endpoint) => endpoint) + : createEntityResource(name, () => endpoint) const transformer = (rawResult) => [ deepKeysToCamelCase(rawResult.results), rawResult.count, diff --git a/src/client/components/Resource/index.jsx b/src/client/components/Resource/index.jsx index 0531ef94f9e..2519da23f75 100644 --- a/src/client/components/Resource/index.jsx +++ b/src/client/components/Resource/index.jsx @@ -5,6 +5,7 @@ export { default as BreakdownTypeResource } from './BreakdownType' export { default as BusinessPotentialResource } from './BusinessPotential' export { default as BusinessActivitiesResource } from './BusinessActivities' export { default as CompanyResource } from './Company' +export { default as CompanyAuditHistoryResource } from './CompanyAuditHistory' export { default as CompanyContactsResource } from './CompanyContacts' export { default as CompanyObjectivesCountResource } from './CompanyObjectivesCount' export { default as CompanyObjectivesResource } from './CompanyObjectives' diff --git a/src/client/components/Resource/tasks.js b/src/client/components/Resource/tasks.js index fbc86d3bc86..afc06257043 100644 --- a/src/client/components/Resource/tasks.js +++ b/src/client/components/Resource/tasks.js @@ -87,6 +87,7 @@ import TaskCompletedSettings from './TaskCompletedSettings' import OmisMarkets from './OmisMarkets' import OrderPayment from './OrderPayment' import TaskCompaniesAndProjects from './TaskCompaniesAndProjects' +import CompanyAuditHistory from './CompanyAuditHistory' export default { ...AssociatedProgramme.tasks, @@ -178,4 +179,5 @@ export default { ...OmisMarkets.tasks, ...OrderPayment.tasks, ...TaskCompaniesAndProjects.tasks, + ...CompanyAuditHistory.tasks, } diff --git a/src/client/index.jsx b/src/client/index.jsx index ee52afe1d61..90dbf499d30 100644 --- a/src/client/index.jsx +++ b/src/client/index.jsx @@ -12,7 +12,6 @@ import Provider from './provider' import AddCompanyForm from '../apps/companies/apps/add-company/client/AddCompanyForm' import InteractionDetailsForm from '../apps/interactions/apps/details-form/client/InteractionDetailsForm' import EditCompanyForm from '../apps/companies/apps/edit-company/client/EditCompanyForm' -import CompanyEditHistory from '../apps/companies/apps/edit-history/client/CompanyEditHistory' import FindCompany from '../apps/companies/apps/match-company/client/FindCompany' import DeleteCompanyList from '../apps/company-lists/client/DeleteCompanyList' import MatchConfirmation from '../apps/companies/apps/match-company/client/MatchConfirmation' @@ -115,11 +114,6 @@ function App() { )} - - {(props) => ( - - )} - {(props) => ( diff --git a/src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/CompanyEditHistory.jsx b/src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/CompanyEditHistory.jsx new file mode 100644 index 00000000000..e5f445ce71c --- /dev/null +++ b/src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/CompanyEditHistory.jsx @@ -0,0 +1,66 @@ +import React from 'react' +import { useParams } from 'react-router-dom' + +import { + CompanyResource, + CompanyAuditHistoryResource, +} from '../../../../components/Resource' +import { CollectionItem, DefaultLayout } from '../../../../components' +import { transformResponseToCollection } from './transformers' +import urls from '../../../../../lib/urls' + +const CompanyName = ({ id }) => ( + + {(company) => company.name} + +) + +const CompanyEditHistory = () => { + const { companyId } = useParams() + return ( + + Edit history - Business details - - + Companies + + } + heading="Edit history" + breadcrumbs={[ + { link: urls.dashboard.index(), text: 'Home' }, + { + link: urls.companies.index(), + text: 'Companies', + }, + { + link: urls.companies.detail(companyId), + text: , + }, + { + link: urls.companies.businessDetails(companyId), + text: 'Business details', + }, + { text: 'Edit history' }, + ]} + > + + {(companyAuditHistory) => ( +
    + {transformResponseToCollection(companyAuditHistory).map((item) => ( + + ))} +
+ )} +
+
+ ) +} + +export default CompanyEditHistory diff --git a/src/apps/companies/apps/edit-history/constants.js b/src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/constants.js similarity index 53% rename from src/apps/companies/apps/edit-history/constants.js rename to src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/constants.js index d0691fe3080..13ae2b2dc68 100644 --- a/src/apps/companies/apps/edit-history/constants.js +++ b/src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/constants.js @@ -1,17 +1,17 @@ -const ARCHIVED = 'Archived' -const NOT_ARCHIVED = 'Not Archived' -const YES = 'Yes' -const NO = 'No' +export const ARCHIVED = 'Archived' +export const NOT_ARCHIVED = 'Not Archived' +export const YES = 'Yes' +export const NO = 'No' -const CHANGE_TYPE_TEXT = 'business details' +export const CHANGE_TYPE_TEXT = 'business details' -const EXCLUDED_FIELDS = ['archived_by', 'archived_on', 'dnb_modified_on'] +export const EXCLUDED_FIELDS = ['archived_by', 'archived_on', 'dnb_modified_on'] -const COMPANY_FIELD_NAME_TO_LABEL_MAP = { +export const COMPANY_FIELD_NAME_TO_LABEL_MAP = { address_1: 'Address line 1', - address_2: 'Address line 2 (optional)', + address_2: 'Address line 2', company_number: 'Companies House number', - description: 'Business description (optional)', + description: 'Business description', export_experience_category: 'Export win category', export_to_countries: 'Countries currently exporting to', future_interest_countries: 'Future countries of interest', @@ -19,7 +19,7 @@ const COMPANY_FIELD_NAME_TO_LABEL_MAP = { great_profile_status: 'great.gov.uk business profile', is_turnover_estimated: 'Is turnover estimated', name: 'Name of company', - number_of_employees: 'Number of employees (optional)', + number_of_employees: 'Number of employees', one_list_account_owner: 'Global Account Manager - One List', one_list_tier: 'One List tier', pending_dnb_investigation: 'Pending D&B investigation', @@ -29,21 +29,15 @@ const COMPANY_FIELD_NAME_TO_LABEL_MAP = { trading_names: 'Trading name(s)', uk_region: 'DBT region', uk_based: 'UK based', - vat_number: 'VAT number (optional)', - website: "Company's website (optional)", + vat_number: 'VAT number', + website: "Company's website", } -const AUTOMATIC_UPDATE = 'automaticUpdate' -const NOT_SET = 'Not set' - -module.exports = { - ARCHIVED, - NOT_ARCHIVED, - YES, - NO, - AUTOMATIC_UPDATE, - CHANGE_TYPE_TEXT, - EXCLUDED_FIELDS, - COMPANY_FIELD_NAME_TO_LABEL_MAP, - NOT_SET, +export const HEADQUARTER_TYPES = { + ehq: 'European HQ', + ghq: 'Global HQ', + ukhq: 'UK HQ', } + +export const AUTOMATIC_UPDATE = 'automaticUpdate' +export const NOT_SET = 'Not set' diff --git a/src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/transformers.js b/src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/transformers.js new file mode 100644 index 00000000000..46056a9d3d2 --- /dev/null +++ b/src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/transformers.js @@ -0,0 +1,121 @@ +import { isBoolean, isNumber, isEmpty, capitalize, lowerCase } from 'lodash' + +import { formatMediumDateTime, isDateValid } from '../../../../utils/date' +import { + EXCLUDED_FIELDS, + COMPANY_FIELD_NAME_TO_LABEL_MAP, + AUTOMATIC_UPDATE, + YES, + NO, + ARCHIVED, + NOT_ARCHIVED, + NOT_SET, + HEADQUARTER_TYPES, +} from './constants' +import { currencyGBP } from '../../../../utils/number-utils' + +const mapFieldNameToLabel = (fieldName) => + COMPANY_FIELD_NAME_TO_LABEL_MAP[fieldName] || capitalize(lowerCase(fieldName)) + +const unwrapFromArray = (change) => + Array.isArray(change) + ? change.length > 1 + ? change + : change[0] || null + : change + +const transformChanges = (changes) => + Object.keys(changes) + .filter((fieldName) => !EXCLUDED_FIELDS.includes(fieldName)) + .map((fieldName) => ({ + fieldName: mapFieldNameToLabel(fieldName), + oldValue: unwrapFromArray(changes[fieldName][0]), + newValue: unwrapFromArray(changes[fieldName][1]), + })) + +const getUpdatedBy = (timestamp, user) => { + const formattedTime = formatMediumDateTime(timestamp) + const changedBy = user + ? isEmpty(user?.name) + ? user?.email + : user?.name + : AUTOMATIC_UPDATE + return changedBy === AUTOMATIC_UPDATE + ? `Automatically updated on ${formattedTime}` + : `Updated on ${formattedTime} by ${changedBy}` +} + +const getValueFromBoolean = (value, field) => { + if ( + field === 'Is number of employees estimated' || + field === 'Is turnover estimated' + ) { + return value ? YES : NO + } + + if (field === 'Archived') { + return value ? ARCHIVED : NOT_ARCHIVED + } +} + +const getValue = (value, field) => + isBoolean(value) + ? getValueFromBoolean(value, field) + : isNumber(value) + ? field === 'Turnover' + ? currencyGBP(value, { + maximumSignificantDigits: 2, + }) + : value.toString() + : isDateValid(value) + ? formatMediumDateTime(value) + : field === 'Headquarter type' + ? HEADQUARTER_TYPES[value] || NOT_SET + : value || NOT_SET + +const getBadgeText = (length) => + length == 1 ? '1 change' : length + ' changes' + +const transformCompanyAuditLogItem = (logItem) => { + const changesSaved = !!Object.keys(logItem.changes).length + const metadata = [] + const transformedChanges = transformChanges(logItem.changes) + + changesSaved + ? transformedChanges.map( + (change, index) => + metadata.push({ + key: `${logItem.id}-${index}-field-name`, + value: change.fieldName, + }) && + metadata.push({ + key: `${logItem.id}-${index}-old-value`, + label: 'Information before change: ', + value: getValue(change.oldValue, change.fieldName), + }) && + metadata.push({ + label: 'Information after change: ', + key: `${logItem.id}-${index}-new-value`, + value: getValue(change.newValue, change.fieldName), + }) + ) + : metadata.push({ + value: 'No changes were made to business details in this update', + }) + + const badges = [ + { + text: changesSaved ? getBadgeText(transformedChanges.length) : '', + }, + ] + + return { + id: logItem.id, + metadata: metadata, + badges: badges.filter((item) => item.text), + headingText: getUpdatedBy(logItem.timestamp, logItem.user), + } +} + +export const transformResponseToCollection = (results = []) => + results.map((result) => transformCompanyAuditLogItem(result)) diff --git a/src/client/routes.js b/src/client/routes.js index e41a412a072..aa750b96fd8 100644 --- a/src/client/routes.js +++ b/src/client/routes.js @@ -97,6 +97,7 @@ import ExportsHistory from './modules/Companies/CompanyExports/ExportHistory' import InvestmentProjectAdmin from './modules/Investments/Projects/InvestmentProjectAdmin' import OrderQuote from './modules/Omis/OrderQuote' import OrdersReconciliationCollection from './modules/Omis/CollectionList/OrdersReconciliationCollection' +import CompanyEditHistory from './modules/Companies/CompanyBusinessDetails/CompanyEditHistory/CompanyEditHistory' const routes = { companies: [ @@ -250,6 +251,11 @@ const routes = { module: 'datahub:companies', component: ExportsHistory, }, + { + path: '/companies/:companyId/edit-history', + module: 'datahub:companies', + component: CompanyEditHistory, + }, ], contacts: [ { diff --git a/test/end-to-end/cypress/specs/DIT/audit-spec.js b/test/end-to-end/cypress/specs/DIT/audit-spec.js index d759711804d..a5d3cafba30 100644 --- a/test/end-to-end/cypress/specs/DIT/audit-spec.js +++ b/test/end-to-end/cypress/specs/DIT/audit-spec.js @@ -6,6 +6,12 @@ const { DATE_MEDIUM_FORMAT } = require('../../../../../src/common/constants') const { assertFlashMessage, } = require('../../../../functional/cypress/support/assertions') +const { + NOT_SET, +} = require('../../../../../src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/constants') +const { + assertBadge, +} = require('../../../../functional/cypress/support/collection-list-assertions') const todaysDate = formatWithoutParsing(new Date(), DATE_MEDIUM_FORMAT) let companyObj @@ -30,11 +36,21 @@ describe('Company', () => { ) cy.visit(urls.companies.editHistory.index(companyObj.pk)) + cy.get('[data-test="collection-item"]').as('collectionItems') + cy.get('@collectionItems').eq(0).as('listItem1') - cy.get(selectors.editHistory.change(1).updated) + cy.get('@listItem1') .should('contain', todaysDate) .and('contain', 'DIT Staff') - cy.get(selectors.companyEdit.history).should('contain', '1 change') + .and('contain', "Company's website") + .and('contain', NOT_SET) + .and('contain', 'http://www.example.com') + + cy.get('[data-test="collection-header-name"]') + .should('exist') + .should('contain', '1 change') + + assertBadge('@listItem1', '1 change') }) }) diff --git a/test/functional/cypress/specs/companies/edit-history-spec.js b/test/functional/cypress/specs/companies/edit-history-spec.js index 251231e0a83..3729f4c56ff 100644 --- a/test/functional/cypress/specs/companies/edit-history-spec.js +++ b/test/functional/cypress/specs/companies/edit-history-spec.js @@ -1,30 +1,40 @@ -const { - assertLocalHeader, - assertBreadcrumbs, -} = require('../../support/assertions') -const fixtures = require('../../fixtures') -const { editHistory } = require('../../../../selectors') -const urls = require('../../../../../src/lib/urls') - -const assertChanges = (table, caption, beforeChange, afterChange) => { - cy.get(table.caption).should('have.text', caption) - cy.get(table.beforeChangeText).should( - 'have.text', - 'Information before change' - ) - cy.get(table.beforeChangeValue).should('have.text', beforeChange) - cy.get(table.afterChangeText).should('have.text', 'Information after change') - cy.get(table.afterChangeValue).should('have.text', afterChange) +import { assertLocalHeader, assertBreadcrumbs } from '../../support/assertions' +import { + assertBadge, + assertBadgeNotPresent, +} from '../../support/collection-list-assertions' +import fixtures from '../../fixtures' +import urls from '../../../../../src/lib/urls' +import { NOT_SET } from '../../../../../src/client/modules/Companies/CompanyBusinessDetails/CompanyEditHistory/constants' + +const assertChanges = (itemNo, field, oldVal, newVal) => { + it(`should display the changes to "${field}"`, () => { + cy.get(`@listItem${itemNo}`) + .should('contain', field) + .and('contain', oldVal) + .and('contain', newVal) + }) } describe('Edit History', () => { beforeEach(() => { cy.visit(urls.companies.editHistory.index(fixtures.company.venusLtd.id)) + cy.get('[data-test="collection-item"]').as('collectionItems') + cy.get('@collectionItems').eq(0).as('listItem1') + cy.get('@collectionItems').eq(1).as('listItem2') + cy.get('@collectionItems').eq(2).as('listItem3') + cy.get('@collectionItems').eq(3).as('listItem4') + cy.get('@collectionItems').eq(4).as('listItem5') + cy.get('@collectionItems').eq(5).as('listItem6') + cy.get('@collectionItems').eq(6).as('listItem7') + cy.get('@collectionItems').eq(7).as('listItem8') + cy.get('@collectionItems').eq(8).as('listItem9') + cy.get('@collectionItems').eq(9).as('listItem10') }) context('when viewing the "Edit History" page', () => { it('should render the header', () => { - assertLocalHeader('Edit History') + assertLocalHeader('Edit history') }) it('should render breadcrumbs', () => { @@ -35,109 +45,64 @@ describe('Edit History', () => { 'Business details': urls.companies.businessDetails( fixtures.company.venusLtd.id ), - 'Edit History': null, + 'Edit history': null, }) }) - }) - context('when viewing an address change', () => { - it('should display the date when the update occurred and by whom', () => { - cy.get(editHistory.change(1).updated).should( - 'have.text', - 'Updated on 10 Dec 2019, 5:58pm by Paul Gain' - ) + it('should render the collection header', () => { + cy.get('[data-test="collection-header-name"]') + .should('exist') + .should('contain', '11 changes to company business details') }) - it('should display the changes to "Address line 1"', () => { - assertChanges( - editHistory.change(1).table(2), - 'Address line 1', - '14 Wharf Road', - '16 Wharf Road' - ) - }) - - it('should display the changes to "Address line 2 (optional)"', () => { - assertChanges( - editHistory.change(1).table(3), - 'Address line 2 (optional)', - 'Not set', - 'Westminster' - ) - }) - - it('should display the changes to "Address town"', () => { - assertChanges( - editHistory.change(1).table(4), - 'Address town', - 'Brentwood', - 'London' - ) + it('should render the correct badges', () => { + assertBadge('@listItem1', '5 changes') + assertBadge('@listItem3', '1 change') + assertBadgeNotPresent('@listItem4') }) + }) - it('should display the changes to "Address county"', () => { - assertChanges( - editHistory.change(1).table(5), - 'Address county', - 'Essex', - 'Greater London' + context('when viewing an address change', () => { + it('should display the date when the update occurred and by whom', () => { + cy.get('@listItem1').should( + 'contain', + 'Updated on 10 Dec 2019, 5:58pm by Paul Gain' ) }) - it('should display the changes to "Address postcode"', () => { - assertChanges( - editHistory.change(1).table(6), - 'Address postcode', - 'CM14 4LQ', - 'SW1H 9AJ' - ) - }) + assertChanges(1, 'Address line 1', '14 Wharf Road', '16 Wharf Road') + assertChanges(1, 'Address line 2', NOT_SET, 'Westminster') + assertChanges(1, 'Address town', 'Brentwood', 'London') + assertChanges(1, 'Address county', 'Essex', 'Greater London') + assertChanges(1, 'Address postcode', 'CM14 4LQ', 'SW1H 9AJ') }) context('when viewing a sector, region and description change', () => { - it('should display the changes to "Business description (optional)"', () => { - assertChanges( - editHistory.change(2).table(2), - 'Business description (optional)', - 'Not set', - 'Superior editing services' - ) - }) - - it('should display the changes to "DBT sector"', () => { - assertChanges( - editHistory.change(2).table(3), - 'DBT sector', - 'Biotechnology and Pharmaceuticals', - 'Airports' - ) - }) - - it('should display the changes to "DBT region"', () => { - assertChanges( - editHistory.change(2).table(4), - 'DBT region', - 'South East', - 'London' - ) - }) + assertChanges( + 2, + 'Business description', + NOT_SET, + 'Superior editing services' + ) + + assertChanges( + 2, + 'DBT sector', + 'Biotechnology and Pharmaceuticals', + 'Airports' + ) + + assertChanges(2, 'DBT region', 'South East', 'London') }) context('when viewing a "Trading name" change', () => { - it('should display the changes to "Trading name(s)"', () => { - assertChanges( - editHistory.change(3).table(2), - 'Trading name(s)', - 'Not set', - 'Edit History Enterprises' - ) - }) + assertChanges(3, 'Trading name(s)', NOT_SET, 'Edit History Enterprises') }) context('when the user does not have a first or last name', () => { it("should display the user's email address", () => { - cy.get(editHistory.change(4).updated).should( - 'have.text', + cy.get('@listItem4').should( + 'contain', 'Updated on 10 Dec 2019, 6:01pm by paul.gain@digital.trade.gov.uk' ) }) @@ -145,119 +110,66 @@ describe('Edit History', () => { context('when the user is null it must be an "Automatic update"', () => { it('should display that is was automatically updated', () => { - cy.get(editHistory.change(5).updated).should( - 'have.text', + cy.get('@listItem5').should( + 'contain', 'Automatically updated on 9 Jan 2020, 12:00am' ) }) - it('should update the turnover', () => { - assertChanges( - editHistory.change(5).table(2), - 'Turnover', - '£2,400,000', - '£1,800,000' - ) - }) - - it('should show whether or not the turnover was estimated', () => { - assertChanges( - editHistory.change(5).table(3), - 'Is turnover estimated', - 'Yes', - 'Not set' - ) - }) - - it('should show whether or not the number of employees was estimated', () => { - assertChanges( - editHistory.change(5).table(4), - 'Is number of employees estimated', - 'Not set', - 'Yes' - ) - }) + assertChanges(5, 'Turnover', '£2,400,000', '£1,800,000') + assertChanges(5, 'Is turnover estimated', 'Yes', NOT_SET) + assertChanges(5, 'Is number of employees estimated', NOT_SET, 'Yes') }) context('when the user saves without making changes', () => { it('should display a message', () => { - cy.get(editHistory.change(6).noChanges).should( - 'have.text', + cy.get('@listItem6').should( + 'contain', 'No changes were made to business details in this update' ) }) }) - context('when the user updates "Number of employees (optional)"', () => { - it('should display the number of employees', () => { - assertChanges( - editHistory.change(7).table(2), - 'Number of employees (optional)', - '98771', - '98772' - ) - }) + context('when the user updates "Number of employees"', () => { + assertChanges(7, 'Number of employees', '98771', '98772') }) context('when the user has unarchived a company', () => { - it('should show the company is no longer archived', () => { - assertChanges( - editHistory.change(8).table(2), - 'Archived', - 'Archived', - 'Not Archived' - ) - }) - - it('should show the reason why it was unarchived', () => { - assertChanges( - editHistory.change(8).table(3), - 'Archived reason', - 'Archived by mistake', - 'Not set' - ) - }) + assertChanges(8, 'Archived', 'Archived', 'Not Archived') + assertChanges(8, 'Archived reason', 'Archived by mistake', NOT_SET) }) context('when the user has archived a company', () => { - it('should show the company is archived', () => { - assertChanges( - editHistory.change(9).table(2), - 'Archived', - 'Not Archived', - 'Archived' - ) - }) - - it('should show the reason why it was archived', () => { - assertChanges( - editHistory.change(9).table(3), - 'Archived reason', - 'Not set', - 'Company is dissolved' - ) - }) + assertChanges(9, 'Archived', 'Not Archived', 'Archived') + assertChanges(9, 'Archived reason', NOT_SET, 'Company is dissolved') }) context('when a company becomes a "Global Ultimate"', () => { - it('should display the changes to the "Global Ultimate Duns Number"', () => { - assertChanges( - editHistory.change(5).table(5), - 'Global Ultimate Duns Number', - 'Not set', - '561652707' - ) - }) + assertChanges(5, 'Global Ultimate Duns Number', NOT_SET, '561652707') + }) + + context('when viewing a HQ change', () => { + assertChanges(7, 'Headquarter type', 'European HQ', 'UK HQ') + assertChanges(8, 'Headquarter type', 'Global HQ', 'European HQ') + assertChanges(10, 'Headquarter type', NOT_SET, 'Global HQ') }) context('when there are more than 10 entries', () => { it('should display the pagination', () => { - cy.get('[data-test="page-number"]').should('be.visible', true) + cy.get('[data-page-number="2"]').should('be.visible', true) + cy.get('[data-test="pagination-summary"]').should( + 'contain', + 'Page 1 of 2' + ) }) - it('should display the pagination', () => { - cy.get('[data-test="page-number"]').click() + it('should render the next page when the button is clicked', () => { + cy.get('[data-page-number="2"]').click() cy.url().should('include', '?page=2') + cy.get('[data-test="pagination-summary"]').should( + 'contain', + 'Page 2 of 2' + ) }) }) }) diff --git a/test/functional/cypress/support/collection-list-assertions.js b/test/functional/cypress/support/collection-list-assertions.js index 9f10d6a9b81..caad8c47965 100644 --- a/test/functional/cypress/support/collection-list-assertions.js +++ b/test/functional/cypress/support/collection-list-assertions.js @@ -52,8 +52,8 @@ const assertTag = (item, badgeText) => { .should('contain', badgeText) } -const assertBadgeNotPresent = (item, badgeText) => { - cy.get(item).find('[data-test="badge"]').should('not.contain', badgeText) +const assertBadgeNotPresent = (item) => { + cy.get(item).find('[data-test="badge"]').should('not.exist') } const assertTagNotPresent = (item, badgeText) => { diff --git a/test/sandbox/fixtures/v4/company-audit/company-audit.json b/test/sandbox/fixtures/v4/company-audit/company-audit.json index 5e7e781cd66..0223f765b16 100644 --- a/test/sandbox/fixtures/v4/company-audit/company-audit.json +++ b/test/sandbox/fixtures/v4/company-audit/company-audit.json @@ -153,6 +153,11 @@ 98771, 98772 ] + , + "headquarter_type": [ + "ehq", + "ukhq" + ] } }, { @@ -182,6 +187,10 @@ "archived_by": [ "Paul Gain", null + ], + "headquarter_type": [ + "ghq", + "ehq" ] } }, @@ -227,21 +236,9 @@ "timestamp": "2020-11-06T14:16:42.923800Z", "comment": "", "changes": { - "archived": [ - false, - true - ], - "archived_on": [ + "headquarter_type": [ null, - "2020-11-06T14:16:42.982000Z" - ], - "archived_reason": [ - null, - "Company is dissolved" - ], - "archived_by": [ - null, - "Pippo Raimondi" + "ghq" ] } }, diff --git a/test/selectors/company/edit.js b/test/selectors/company/edit.js index cc6d9d84e22..8fcd6baf58f 100644 --- a/test/selectors/company/edit.js +++ b/test/selectors/company/edit.js @@ -11,5 +11,4 @@ module.exports = { notHqHierarchy: '#field-headquarter_type > fieldset > div > label:nth-child(2)', saveButton: 'button:contains("Submit")', - history: '#company-edit-history', }