From c4306d7dc305ba7e11a4f069b208285be3eb050d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Wed, 23 Dec 2020 13:35:40 +0300 Subject: [PATCH 01/10] NCI-Agency/anet#3367: Add custom fields to Position (server side) Add a migration for customFields column in positions --- src/main/java/mil/dds/anet/beans/Position.java | 9 +++++---- src/main/java/mil/dds/anet/database/PositionDao.java | 8 ++++---- .../java/mil/dds/anet/resources/PositionResource.java | 2 ++ src/main/resources/migrations.xml | 5 +++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/mil/dds/anet/beans/Position.java b/src/main/java/mil/dds/anet/beans/Position.java index 76edbe2b04..03ca85fbdb 100644 --- a/src/main/java/mil/dds/anet/beans/Position.java +++ b/src/main/java/mil/dds/anet/beans/Position.java @@ -14,10 +14,10 @@ import mil.dds.anet.beans.search.TaskSearchQuery; import mil.dds.anet.utils.IdDataLoaderKey; import mil.dds.anet.utils.Utils; -import mil.dds.anet.views.AbstractAnetBean; +import mil.dds.anet.views.AbstractCustomizableAnetBean; import mil.dds.anet.views.UuidFetcher; -public class Position extends AbstractAnetBean implements RelatableObject, WithStatus { +public class Position extends AbstractCustomizableAnetBean implements RelatableObject, WithStatus { public static enum PositionType { ADVISOR, PRINCIPAL, SUPER_USER, ADMINISTRATOR @@ -272,12 +272,13 @@ public boolean equals(Object o) { return Objects.equals(uuid, other.getUuid()) && Objects.equals(name, other.getName()) && Objects.equals(code, other.getCode()) && Objects.equals(type, other.getType()) && Objects.equals(status, other.getStatus()) - && Objects.equals(getOrganizationUuid(), other.getOrganizationUuid()); + && Objects.equals(getOrganizationUuid(), other.getOrganizationUuid()) + && Objects.equals(customFields, other.getCustomFields()); } @Override public int hashCode() { - return Objects.hash(uuid, name, code, type, status, organization); + return Objects.hash(uuid, name, code, type, status, organization, customFields); } @Override diff --git a/src/main/java/mil/dds/anet/database/PositionDao.java b/src/main/java/mil/dds/anet/database/PositionDao.java index 8b46314da6..504b04092e 100644 --- a/src/main/java/mil/dds/anet/database/PositionDao.java +++ b/src/main/java/mil/dds/anet/database/PositionDao.java @@ -31,7 +31,7 @@ public class PositionDao extends AnetBaseDao { public static String[] fields = {"uuid", "name", "code", "createdAt", "updatedAt", - "organizationUuid", "currentPersonUuid", "type", "status", "locationUuid"}; + "organizationUuid", "currentPersonUuid", "type", "status", "locationUuid", "customFields"}; public static String TABLE_NAME = "positions"; public static String POSITIONS_FIELDS = DaoUtils.buildFieldAliases(TABLE_NAME, fields, true); @@ -45,8 +45,8 @@ public Position insertInternal(Position p) { try { getDbHandle() .createUpdate("/* positionInsert */ INSERT INTO positions (uuid, name, code, type, " - + "status, \"organizationUuid\", \"locationUuid\", \"createdAt\", \"updatedAt\") " - + "VALUES (:uuid, :name, :code, :type, :status, :organizationUuid, :locationUuid, :createdAt, :updatedAt)") + + "status, \"organizationUuid\", \"locationUuid\", \"createdAt\", \"updatedAt\", \"customFields\") " + + "VALUES (:uuid, :name, :code, :type, :status, :organizationUuid, :locationUuid, :createdAt, :updatedAt, :customFields)") .bindBean(p).bind("createdAt", DaoUtils.asLocalDateTime(p.getCreatedAt())) .bind("updatedAt", DaoUtils.asLocalDateTime(p.getUpdatedAt())) .bind("type", DaoUtils.getEnumId(p.getType())) @@ -159,7 +159,7 @@ public int updateInternal(Position p) { final int nr = getDbHandle() .createUpdate("/* positionUpdate */ UPDATE positions SET name = :name, " + "code = :code, \"organizationUuid\" = :organizationUuid, type = :type, status = :status, " - + "\"locationUuid\" = :locationUuid, \"updatedAt\" = :updatedAt WHERE uuid = :uuid") + + "\"locationUuid\" = :locationUuid, \"updatedAt\" = :updatedAt, \"customFields\" = :customFields WHERE uuid = :uuid") .bindBean(p).bind("updatedAt", DaoUtils.asLocalDateTime(p.getUpdatedAt())) .bind("type", DaoUtils.getEnumId(p.getType())) .bind("status", DaoUtils.getEnumId(p.getStatus())).execute(); diff --git a/src/main/java/mil/dds/anet/resources/PositionResource.java b/src/main/java/mil/dds/anet/resources/PositionResource.java index cfb6114ea5..057d031311 100644 --- a/src/main/java/mil/dds/anet/resources/PositionResource.java +++ b/src/main/java/mil/dds/anet/resources/PositionResource.java @@ -70,6 +70,7 @@ private void assertCanUpdatePosition(Person user, Position pos) { @GraphQLMutation(name = "createPosition") public Position createPosition(@GraphQLRootContext Map context, @GraphQLArgument(name = "position") Position pos) { + pos.checkAndFixCustomFields(); final Person user = DaoUtils.getUserFromContext(context); assertCanUpdatePosition(user, pos); validatePosition(user, pos); @@ -114,6 +115,7 @@ public Integer updateAssociatedPosition(@GraphQLRootContext Map @GraphQLMutation(name = "updatePosition") public Integer updatePosition(@GraphQLRootContext Map context, @GraphQLArgument(name = "position") Position pos) { + pos.checkAndFixCustomFields(); final Person user = DaoUtils.getUserFromContext(context); assertCanUpdatePosition(user, pos); validatePosition(user, pos); diff --git a/src/main/resources/migrations.xml b/src/main/resources/migrations.xml index 3bd72d8b1b..e6e4bd1b7a 100644 --- a/src/main/resources/migrations.xml +++ b/src/main/resources/migrations.xml @@ -3640,4 +3640,9 @@ + + + + + From 49ac9d0ac7e2de872be63a9098dbfe141b0890e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Wed, 23 Dec 2020 13:14:27 +0300 Subject: [PATCH 02/10] NCI-Agency/anet#3367: Add custom fields to Position (client side) --- anet-dictionary.yml | 78 +++++++++++++++++++ .../MultiTypeAdvancedSelectComponent.js | 2 +- client/src/models/Position.js | 14 +++- .../pages/admin/authorizationgroup/Form.js | 10 ++- client/src/pages/positions/Edit.js | 27 ++++++- client/src/pages/positions/Form.js | 29 +++++-- client/src/pages/positions/New.js | 19 ++++- client/src/pages/positions/Show.js | 19 ++++- src/main/resources/anet-schema.yml | 4 + 9 files changed, 190 insertions(+), 12 deletions(-) diff --git a/anet-dictionary.yml b/anet-dictionary.yml index f651fd3635..bc5f5dae0f 100644 --- a/anet-dictionary.yml +++ b/anet-dictionary.yml @@ -479,6 +479,84 @@ fields: position: name: 'Position Name' + customFields: + multipleButtons: + type: enumset + label: Choose one or more of the options + helpText: Help text for choosing multiple values + choices: + opt1: + label: Option 1 + opt2: + label: Option 2 + opt3: + label: Option 3 + inputFieldName: + type: text + label: Text field + placeholder: Placeholder text for input field + helpText: Help text for text field + colourOptions: + type: enum + label: Choose one of the colours + helpText: Help text for choosing colours + choices: + GREEN: + label: Green + color: '#c2ffb3' + AMBER: + label: Amber + color: '#ffe396' + RED: + label: Red + color: '#ff8279' + textareaFieldName: + type: text + label: Textarea field + placeholder: Placeholder text for textarea field + helpText: Help text for textarea field + componentClass: textarea + style: + height: 200px + visibleWhen: $[?(@.colourOptions === 'GREEN')] + numberFieldName: + type: number + typeError: Number field must be a number + label: Number field + placeholder: Placeholder text for number field + helpText: Help text for number field + validations: + - type: integer + - type: min + params: [5] + - type: max + params: [100] + visibleWhen: $[?((@.colourOptions === 'GREEN')||(@.colourOptions === 'RED'))] + nlt: + type: date + label: Not later than date + helpText: Help text for date field + nlt_dt: + type: datetime + label: Not later than datetime + helpText: Help text for datetime field + arrayFieldName: + type: array_of_objects + label: Array of objects + helpText: Here you can add as many objects as needed + addButtonLabel: Add an object + objectLabel: Object + objectFields: + textF: + type: text + label: Object text + placeholder: Placeholder text for object text field + helpText: Help text for object text field + dateF: + type: date + label: Object date + helpText: Help text for object date field + visibleWhen: $[?(@.colourOptions === 'GREEN')] organization: shortName: Name diff --git a/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js b/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js index 709dc84c53..ae5beeb96a 100644 --- a/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js +++ b/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js @@ -84,7 +84,7 @@ const widgetPropsPosition = { overlayColumns: ["Position", "Organization", "Current Occupant"], filterDefs: entityFilters, queryParams: { status: Model.STATUS.ACTIVE }, - fields: Models.Position.autocompleteQuery, + fields: Models.Position.autocompleteQueryWithNotes, addon: POSITIONS_ICON } diff --git a/client/src/models/Position.js b/client/src/models/Position.js index b656a500f7..09ebb23c21 100644 --- a/client/src/models/Position.js +++ b/client/src/models/Position.js @@ -1,4 +1,7 @@ -import Model from "components/Model" +import Model, { + createCustomFieldsSchema, + GRAPHQL_NOTES_FIELDS +} from "components/Model" import AFG_ICON from "resources/afg_small.png" import POSITIONS_ICON from "resources/positions.png" import RS_ICON from "resources/rs_small.png" @@ -24,6 +27,11 @@ export default class Position extends Model { ADMINISTRATOR: "ADMINISTRATOR" } + // create yup schema for the customFields, based on the customFields config + static customFieldsSchema = createCustomFieldsSchema( + Settings.fields.position.customFields + ) + static yupSchema = yup .object() .shape({ @@ -57,11 +65,15 @@ export default class Position extends Model { person: yup.object().nullable().default({}), location: yup.object().nullable().default({}) }) + // not actually in the database, the database contains the JSON customFields + .concat(Position.customFieldsSchema) .concat(Model.yupSchema) static autocompleteQuery = "uuid, name, code, type, status, organization { uuid, shortName}, person { uuid, name, rank, role, avatar(size: 32) }" + static autocompleteQueryWithNotes = `${this.autocompleteQuery} ${GRAPHQL_NOTES_FIELDS}` + static humanNameOfStatus(status) { return utils.sentenceCase(status) } diff --git a/client/src/pages/admin/authorizationgroup/Form.js b/client/src/pages/admin/authorizationgroup/Form.js index 6d32357adc..8d6df87ea6 100644 --- a/client/src/pages/admin/authorizationgroup/Form.js +++ b/client/src/pages/admin/authorizationgroup/Form.js @@ -5,7 +5,7 @@ import { PositionOverlayRow } from "components/advancedSelectWidget/AdvancedSele import * as FieldHelper from "components/FieldHelper" import Fieldset from "components/Fieldset" import Messages from "components/Messages" -import Model from "components/Model" +import Model, { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import NavigationWarning from "components/NavigationWarning" import { jumpToTop } from "components/Page" import PositionTable from "components/PositionTable" @@ -233,6 +233,14 @@ const AuthorizationGroupForm = ({ edit, title, initialValues }) => { new AuthorizationGroup(values), "notes" ) + authorizationGroup.positions = values.positions.map(pos => { + const p = Object.without( + pos, + "customFields", + DEFAULT_CUSTOM_FIELDS_PARENT + ) + return p + }) return API.mutation( edit ? GQL_UPDATE_AUTHORIZATION_GROUP : GQL_CREATE_AUTHORIZATION_GROUP, { authorizationGroup } diff --git a/client/src/pages/positions/Edit.js b/client/src/pages/positions/Edit.js index b9a1617fc2..ccb252e698 100644 --- a/client/src/pages/positions/Edit.js +++ b/client/src/pages/positions/Edit.js @@ -1,9 +1,14 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" +import { getInvisibleFields } from "components/CustomFields" +import { + DEFAULT_CUSTOM_FIELDS_PARENT, + INVISIBLE_CUSTOM_FIELDS_FIELD +} from "components/Model" import { - PageDispatchersPropType, mapPageDispatchersToProps, + PageDispatchersPropType, useBoilerplate } from "components/Page" import RelatedObjectNotes, { @@ -13,6 +18,8 @@ import { Position } from "models" import React from "react" import { connect } from "react-redux" import { useParams } from "react-router-dom" +import Settings from "settings" +import utils from "utils" import PositionForm from "./Form" const GQL_GET_POSITION = gql` @@ -53,6 +60,7 @@ const GQL_GET_POSITION = gql` role avatar(size: 32) } + customFields ${GRAPHQL_NOTES_FIELDS} } } @@ -76,8 +84,23 @@ const PositionEdit = ({ pageDispatchers }) => { return result } - const position = new Position(data ? data.position : {}) + if (data) { + data.position[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( + data.position.customFields + ) + } + const position = new Position(data ? data.position : {}) + if (position[DEFAULT_CUSTOM_FIELDS_PARENT]) { + // set initial invisible custom fields + position[DEFAULT_CUSTOM_FIELDS_PARENT][ + INVISIBLE_CUSTOM_FIELDS_FIELD + ] = getInvisibleFields( + Settings.fields.position.customFields, + DEFAULT_CUSTOM_FIELDS_PARENT, + position + ) + } return (
{ initialValues={initialValues} > {({ - handleSubmit, isSubmitting, dirty, - errors, setFieldValue, setFieldTouched, values, + validateForm, submitForm }) => { const isPrincipal = values.type === Position.TYPE.PRINCIPAL @@ -298,7 +301,19 @@ const PositionForm = ({ edit, title, initialValues }) => { } /> - + {Settings.fields.position.customFields && ( +
+ +
+ )}
@@ -358,7 +373,9 @@ const PositionForm = ({ edit, title, initialValues }) => { const position = Object.without( new Position(values), "notes", - "responsibleTasks" // Only for querying + "customFields", // initial JSON from the db + "responsibleTasks", // Only for querying + DEFAULT_CUSTOM_FIELDS_PARENT ) if (position.type !== Position.TYPE.PRINCIPAL) { position.type = position.permissions || Position.TYPE.ADVISOR @@ -370,6 +387,8 @@ const PositionForm = ({ edit, title, initialValues }) => { position.organization = utils.getReference(position.organization) position.person = utils.getReference(position.person) position.code = position.code || null // Need to null out empty position codes + position.customFields = customFieldsJSONString(values) + return API.mutation(edit ? GQL_UPDATE_POSITION : GQL_CREATE_POSITION, { position }) diff --git a/client/src/pages/positions/New.js b/client/src/pages/positions/New.js index cdecae7bf3..ff9cf57c55 100644 --- a/client/src/pages/positions/New.js +++ b/client/src/pages/positions/New.js @@ -1,9 +1,14 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" +import { getInvisibleFields } from "components/CustomFields" +import { + DEFAULT_CUSTOM_FIELDS_PARENT, + INVISIBLE_CUSTOM_FIELDS_FIELD +} from "components/Model" import { - PageDispatchersPropType, mapPageDispatchersToProps, + PageDispatchersPropType, useBoilerplate } from "components/Page" import { Organization, Position } from "models" @@ -11,6 +16,7 @@ import PropTypes from "prop-types" import React from "react" import { connect } from "react-redux" import { useLocation } from "react-router-dom" +import Settings from "settings" import utils from "utils" import PositionForm from "./Form" @@ -93,6 +99,17 @@ const PositionNewConditional = ({ : Position.TYPE.PRINCIPAL } + if (position[DEFAULT_CUSTOM_FIELDS_PARENT]) { + // set initial invisible custom fields + position[DEFAULT_CUSTOM_FIELDS_PARENT][ + INVISIBLE_CUSTOM_FIELDS_FIELD + ] = getInvisibleFields( + Settings.fields.position.customFields, + DEFAULT_CUSTOM_FIELDS_PARENT, + position + ) + } + return } diff --git a/client/src/pages/positions/Show.js b/client/src/pages/positions/Show.js index 6743288306..d5a4f887c6 100644 --- a/client/src/pages/positions/Show.js +++ b/client/src/pages/positions/Show.js @@ -4,13 +4,14 @@ import { gql } from "apollo-boost" import AppContext from "components/AppContext" import AssignPersonModal from "components/AssignPersonModal" import ConfirmDelete from "components/ConfirmDelete" +import { ReadonlyCustomFields } from "components/CustomFields" import EditAssociatedPositionsModal from "components/EditAssociatedPositionsModal" import * as FieldHelper from "components/FieldHelper" import Fieldset from "components/Fieldset" import GuidedTour from "components/GuidedTour" import LinkTo from "components/LinkTo" import Messages from "components/Messages" -import Model from "components/Model" +import Model, { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import { jumpToTop, mapPageDispatchersToProps, @@ -30,6 +31,7 @@ import { Button, Table } from "react-bootstrap" import { connect } from "react-redux" import { useHistory, useLocation, useParams } from "react-router-dom" import Settings from "settings" +import utils from "utils" const GQL_GET_POSITION = gql` query($uuid: String!) { @@ -83,6 +85,7 @@ const GQL_GET_POSITION = gql` uuid name } + customFields ${GRAPHQL_NOTES_FIELDS} } @@ -124,6 +127,12 @@ const PositionShow = ({ pageDispatchers }) => { return result } + if (data) { + data.position[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( + data.position.customFields + ) + } + const position = new Position(data ? data.position : {}) const CodeFieldWithLabel = DictionaryField(Field) @@ -369,6 +378,14 @@ const PositionShow = ({ pageDispatchers }) => { + {Settings.fields.position.customFields && ( +
+ +
+ )} {canDelete && ( diff --git a/src/main/resources/anet-schema.yml b/src/main/resources/anet-schema.yml index e3285d633e..c3896c719c 100644 --- a/src/main/resources/anet-schema.yml +++ b/src/main/resources/anet-schema.yml @@ -533,6 +533,10 @@ properties: type: string title: The label for a position's name description: Used in the UI where a position's name is shown. + customFields: + type: object + additionalProperties: + "$ref": "#/$defs/customField" organization: type: object From 246469e6804e64c2ce98214638d7d50cabd8e744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Wed, 23 Dec 2020 14:19:00 +0300 Subject: [PATCH 03/10] Refactor invisibleFields initialization to a function --- client/src/components/CustomFields.js | 14 ++++++++++++++ client/src/pages/people/Edit.js | 19 ++++--------------- client/src/pages/people/New.js | 18 +++--------------- client/src/pages/positions/Edit.js | 20 +++++--------------- client/src/pages/positions/New.js | 18 +++--------------- client/src/pages/reports/Edit.js | 19 ++++--------------- client/src/pages/reports/New.js | 18 +++--------------- client/src/pages/tasks/Edit.js | 19 ++++--------------- client/src/pages/tasks/New.js | 19 +++---------------- 9 files changed, 43 insertions(+), 121 deletions(-) diff --git a/client/src/components/CustomFields.js b/client/src/components/CustomFields.js index 1d4d4aa94d..3a7434d2d3 100644 --- a/client/src/components/CustomFields.js +++ b/client/src/components/CustomFields.js @@ -660,6 +660,20 @@ const FIELD_COMPONENTS = { [CUSTOM_FIELD_TYPE.ARRAY_OF_ANET_OBJECTS]: ArrayOfAnetObjectsField } +// mutates the object +export function initInvisibleFields( + anetObj, + config, + parentFieldName = DEFAULT_CUSTOM_FIELDS_PARENT +) { + if (anetObj[parentFieldName]) { + // set initial invisible custom fields + anetObj[parentFieldName][ + INVISIBLE_CUSTOM_FIELDS_FIELD + ] = getInvisibleFields(config, parentFieldName, anetObj) + } +} + export function getInvisibleFields( fieldsConfig = {}, parentFieldName, diff --git a/client/src/pages/people/Edit.js b/client/src/pages/people/Edit.js index c04c13ad30..7a3b4ed003 100644 --- a/client/src/pages/people/Edit.js +++ b/client/src/pages/people/Edit.js @@ -1,11 +1,8 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" -import { getInvisibleFields } from "components/CustomFields" -import { - DEFAULT_CUSTOM_FIELDS_PARENT, - INVISIBLE_CUSTOM_FIELDS_FIELD -} from "components/Model" +import { initInvisibleFields } from "components/CustomFields" +import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import { mapPageDispatchersToProps, PageDispatchersPropType, @@ -94,16 +91,8 @@ const PersonEdit = ({ pageDispatchers }) => { ? "Update profile" : "Save Person" - if (person[DEFAULT_CUSTOM_FIELDS_PARENT]) { - // set initial invisible custom fields - person[DEFAULT_CUSTOM_FIELDS_PARENT][ - INVISIBLE_CUSTOM_FIELDS_FIELD - ] = getInvisibleFields( - Settings.fields.person.customFields, - DEFAULT_CUSTOM_FIELDS_PARENT, - person - ) - } + // mutates the object + initInvisibleFields(person, Settings.fields.person.customFields) return (
diff --git a/client/src/pages/people/New.js b/client/src/pages/people/New.js index efc1d9ea77..a4e9505c96 100644 --- a/client/src/pages/people/New.js +++ b/client/src/pages/people/New.js @@ -1,9 +1,5 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" -import { getInvisibleFields } from "components/CustomFields" -import { - DEFAULT_CUSTOM_FIELDS_PARENT, - INVISIBLE_CUSTOM_FIELDS_FIELD -} from "components/Model" +import { initInvisibleFields } from "components/CustomFields" import { mapPageDispatchersToProps, PageDispatchersPropType, @@ -24,16 +20,8 @@ const PersonNew = ({ pageDispatchers }) => { const person = new Person() - if (person[DEFAULT_CUSTOM_FIELDS_PARENT]) { - // set initial invisible custom fields - person[DEFAULT_CUSTOM_FIELDS_PARENT][ - INVISIBLE_CUSTOM_FIELDS_FIELD - ] = getInvisibleFields( - Settings.fields.person.customFields, - DEFAULT_CUSTOM_FIELDS_PARENT, - person - ) - } + // mutates the object + initInvisibleFields(person, Settings.fields.person.customFields) return } diff --git a/client/src/pages/positions/Edit.js b/client/src/pages/positions/Edit.js index ccb252e698..9a3b0d7789 100644 --- a/client/src/pages/positions/Edit.js +++ b/client/src/pages/positions/Edit.js @@ -1,11 +1,8 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" -import { getInvisibleFields } from "components/CustomFields" -import { - DEFAULT_CUSTOM_FIELDS_PARENT, - INVISIBLE_CUSTOM_FIELDS_FIELD -} from "components/Model" +import { initInvisibleFields } from "components/CustomFields" +import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import { mapPageDispatchersToProps, PageDispatchersPropType, @@ -91,16 +88,9 @@ const PositionEdit = ({ pageDispatchers }) => { } const position = new Position(data ? data.position : {}) - if (position[DEFAULT_CUSTOM_FIELDS_PARENT]) { - // set initial invisible custom fields - position[DEFAULT_CUSTOM_FIELDS_PARENT][ - INVISIBLE_CUSTOM_FIELDS_FIELD - ] = getInvisibleFields( - Settings.fields.position.customFields, - DEFAULT_CUSTOM_FIELDS_PARENT, - position - ) - } + // mutates the object + initInvisibleFields(position, Settings.fields.position.customFields) + return (
} diff --git a/client/src/pages/reports/Edit.js b/client/src/pages/reports/Edit.js index 12eb432aab..d5937ecf62 100644 --- a/client/src/pages/reports/Edit.js +++ b/client/src/pages/reports/Edit.js @@ -1,11 +1,8 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" -import { getInvisibleFields } from "components/CustomFields" -import { - DEFAULT_CUSTOM_FIELDS_PARENT, - INVISIBLE_CUSTOM_FIELDS_FIELD -} from "components/Model" +import { initInvisibleFields } from "components/CustomFields" +import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import { mapPageDispatchersToProps, PageDispatchersPropType, @@ -139,16 +136,8 @@ const ReportEdit = ({ pageDispatchers }) => { report.getAttendeesEngagementAssessments() ) - if (reportInitialValues[DEFAULT_CUSTOM_FIELDS_PARENT]) { - // set initial invisible custom fields - reportInitialValues[DEFAULT_CUSTOM_FIELDS_PARENT][ - INVISIBLE_CUSTOM_FIELDS_FIELD - ] = getInvisibleFields( - Settings.fields.report.customFields, - DEFAULT_CUSTOM_FIELDS_PARENT, - report - ) - } + // mutates the object + initInvisibleFields(reportInitialValues, Settings.fields.report.customFields) reportInitialValues.tasks = Task.fromArray(reportInitialValues.tasks) reportInitialValues.reportPeople = Person.fromArray( diff --git a/client/src/pages/reports/New.js b/client/src/pages/reports/New.js index e3468c2515..8d88521276 100644 --- a/client/src/pages/reports/New.js +++ b/client/src/pages/reports/New.js @@ -1,11 +1,7 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import AppContext from "components/AppContext" -import { getInvisibleFields } from "components/CustomFields" +import { initInvisibleFields } from "components/CustomFields" import GuidedTour from "components/GuidedTour" -import { - DEFAULT_CUSTOM_FIELDS_PARENT, - INVISIBLE_CUSTOM_FIELDS_FIELD -} from "components/Model" import { mapPageDispatchersToProps, PageDispatchersPropType, @@ -28,16 +24,8 @@ const ReportNew = ({ pageDispatchers }) => { const report = new Report() - if (report[DEFAULT_CUSTOM_FIELDS_PARENT]) { - // set initial invisible custom fields - report[DEFAULT_CUSTOM_FIELDS_PARENT][ - INVISIBLE_CUSTOM_FIELDS_FIELD - ] = getInvisibleFields( - Settings.fields.report.customFields, - DEFAULT_CUSTOM_FIELDS_PARENT, - report - ) - } + // mutates the object + initInvisibleFields(report, Settings.fields.report.customFields) if (currentUser && currentUser.uuid) { const person = new Person(currentUser) diff --git a/client/src/pages/tasks/Edit.js b/client/src/pages/tasks/Edit.js index 11a86463b3..e4f542ab85 100644 --- a/client/src/pages/tasks/Edit.js +++ b/client/src/pages/tasks/Edit.js @@ -1,11 +1,8 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" -import { getInvisibleFields } from "components/CustomFields" -import { - DEFAULT_CUSTOM_FIELDS_PARENT, - INVISIBLE_CUSTOM_FIELDS_FIELD -} from "components/Model" +import { initInvisibleFields } from "components/CustomFields" +import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import { mapPageDispatchersToProps, PageDispatchersPropType, @@ -125,16 +122,8 @@ const TaskEdit = ({ pageDispatchers }) => { } const task = new Task(data ? data.task : {}) - if (task[DEFAULT_CUSTOM_FIELDS_PARENT]) { - // set initial invisible custom fields - task[DEFAULT_CUSTOM_FIELDS_PARENT][ - INVISIBLE_CUSTOM_FIELDS_FIELD - ] = getInvisibleFields( - Settings.fields.task.customFields, - DEFAULT_CUSTOM_FIELDS_PARENT, - task - ) - } + // mutates the object + initInvisibleFields(task, Settings.fields.task.customFields) return (
diff --git a/client/src/pages/tasks/New.js b/client/src/pages/tasks/New.js index e8f4145297..363fafff94 100644 --- a/client/src/pages/tasks/New.js +++ b/client/src/pages/tasks/New.js @@ -1,11 +1,7 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" -import { getInvisibleFields } from "components/CustomFields" -import { - DEFAULT_CUSTOM_FIELDS_PARENT, - INVISIBLE_CUSTOM_FIELDS_FIELD -} from "components/Model" +import { initInvisibleFields } from "components/CustomFields" import { mapPageDispatchersToProps, PageDispatchersPropType, @@ -92,17 +88,8 @@ const TaskNewConditional = ({ if (data) { task.taskedOrganizations = [new Organization(data.organization)] } - - if (task[DEFAULT_CUSTOM_FIELDS_PARENT]) { - // set initial invisible custom fields - task[DEFAULT_CUSTOM_FIELDS_PARENT][ - INVISIBLE_CUSTOM_FIELDS_FIELD - ] = getInvisibleFields( - Settings.fields.task.customFields, - DEFAULT_CUSTOM_FIELDS_PARENT, - task - ) - } + // mutates the object + initInvisibleFields(task, Settings.fields.task.customFields) return ( Date: Wed, 23 Dec 2020 15:29:04 +0300 Subject: [PATCH 04/10] NCI-Agency/anet#3367: Add custom fields to Organization (server side) --- src/main/java/mil/dds/anet/beans/Organization.java | 10 ++++++---- .../java/mil/dds/anet/database/OrganizationDao.java | 8 ++++---- .../mil/dds/anet/resources/OrganizationResource.java | 2 ++ src/main/resources/migrations.xml | 5 +++++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/java/mil/dds/anet/beans/Organization.java b/src/main/java/mil/dds/anet/beans/Organization.java index 35e97c6263..e03ac02a69 100644 --- a/src/main/java/mil/dds/anet/beans/Organization.java +++ b/src/main/java/mil/dds/anet/beans/Organization.java @@ -19,10 +19,11 @@ import mil.dds.anet.beans.search.TaskSearchQuery; import mil.dds.anet.utils.IdDataLoaderKey; import mil.dds.anet.utils.Utils; -import mil.dds.anet.views.AbstractAnetBean; +import mil.dds.anet.views.AbstractCustomizableAnetBean; import mil.dds.anet.views.UuidFetcher; -public class Organization extends AbstractAnetBean implements RelatableObject, WithStatus { +public class Organization extends AbstractCustomizableAnetBean + implements RelatableObject, WithStatus { /** Pseudo uuid to represent all/top-level organization(s). */ public static final String DUMMY_ORG_UUID = "-1"; @@ -266,13 +267,14 @@ public boolean equals(Object o) { && Objects.equals(other.getLongName(), longName) && Objects.equals(other.getStatus(), status) && Objects.equals(other.getIdentificationCode(), identificationCode) - && Objects.equals(other.getType(), type); + && Objects.equals(other.getType(), type) + && Objects.equals(other.getCustomFields(), customFields); } @Override public int hashCode() { return Objects.hash(uuid, shortName, longName, status, identificationCode, type, createdAt, - updatedAt); + updatedAt, customFields); } @Override diff --git a/src/main/java/mil/dds/anet/database/OrganizationDao.java b/src/main/java/mil/dds/anet/database/OrganizationDao.java index 11af06a81e..226800cbba 100644 --- a/src/main/java/mil/dds/anet/database/OrganizationDao.java +++ b/src/main/java/mil/dds/anet/database/OrganizationDao.java @@ -25,7 +25,7 @@ public class OrganizationDao extends AnetBaseDao { private static String[] fields = {"uuid", "shortName", "longName", "status", "identificationCode", - "type", "createdAt", "updatedAt", "parentOrgUuid"}; + "type", "createdAt", "updatedAt", "parentOrgUuid", "customFields"}; public static String TABLE_NAME = "organizations"; public static String ORGANIZATION_FIELDS = DaoUtils.buildFieldAliases(TABLE_NAME, fields, true); @@ -117,8 +117,8 @@ public List getOrgsByShortNames(List shortNames) { @Override public Organization insertInternal(Organization org) { getDbHandle().createUpdate( - "/* insertOrg */ INSERT INTO organizations (uuid, \"shortName\", \"longName\", status, \"identificationCode\", type, \"createdAt\", \"updatedAt\", \"parentOrgUuid\") " - + "VALUES (:uuid, :shortName, :longName, :status, :identificationCode, :type, :createdAt, :updatedAt, :parentOrgUuid)") + "/* insertOrg */ INSERT INTO organizations (uuid, \"shortName\", \"longName\", status, \"identificationCode\", type, \"createdAt\", \"updatedAt\", \"parentOrgUuid\", \"customFields\") " + + "VALUES (:uuid, :shortName, :longName, :status, :identificationCode, :type, :createdAt, :updatedAt, :parentOrgUuid, :customFields)") .bindBean(org).bind("createdAt", DaoUtils.asLocalDateTime(org.getCreatedAt())) .bind("updatedAt", DaoUtils.asLocalDateTime(org.getUpdatedAt())) .bind("status", DaoUtils.getEnumId(org.getStatus())) @@ -131,7 +131,7 @@ public Organization insertInternal(Organization org) { public int updateInternal(Organization org) { return getDbHandle().createUpdate("/* updateOrg */ UPDATE organizations " + "SET \"shortName\" = :shortName, \"longName\" = :longName, status = :status, \"identificationCode\" = :identificationCode, type = :type, " - + "\"updatedAt\" = :updatedAt, \"parentOrgUuid\" = :parentOrgUuid where uuid = :uuid") + + "\"updatedAt\" = :updatedAt, \"parentOrgUuid\" = :parentOrgUuid, \"customFields\" = :customFields where uuid = :uuid") .bindBean(org).bind("updatedAt", DaoUtils.asLocalDateTime(org.getUpdatedAt())) .bind("status", DaoUtils.getEnumId(org.getStatus())) .bind("type", DaoUtils.getEnumId(org.getType())) diff --git a/src/main/java/mil/dds/anet/resources/OrganizationResource.java b/src/main/java/mil/dds/anet/resources/OrganizationResource.java index d91f6bf2c1..1023354280 100644 --- a/src/main/java/mil/dds/anet/resources/OrganizationResource.java +++ b/src/main/java/mil/dds/anet/resources/OrganizationResource.java @@ -51,6 +51,7 @@ public Organization getByUuid(@GraphQLArgument(name = "uuid") String uuid) { @GraphQLMutation(name = "createOrganization") public Organization createOrganization(@GraphQLRootContext Map context, @GraphQLArgument(name = "organization") Organization org) { + org.checkAndFixCustomFields(); final Person user = DaoUtils.getUserFromContext(context); AuthUtils.assertAdministrator(user); final Organization created; @@ -91,6 +92,7 @@ public Organization createOrganization(@GraphQLRootContext Map c @GraphQLMutation(name = "updateOrganization") public Integer updateOrganization(@GraphQLRootContext Map context, @GraphQLArgument(name = "organization") Organization org) { + org.checkAndFixCustomFields(); final Person user = DaoUtils.getUserFromContext(context); // Verify correct Organization AuthUtils.assertSuperUserForOrg(user, DaoUtils.getUuid(org), false); diff --git a/src/main/resources/migrations.xml b/src/main/resources/migrations.xml index e6e4bd1b7a..1911f5ff97 100644 --- a/src/main/resources/migrations.xml +++ b/src/main/resources/migrations.xml @@ -3645,4 +3645,9 @@ + + + + + From 8a86afa8881e97285da6895f43411f1b4480022f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Wed, 23 Dec 2020 16:18:54 +0300 Subject: [PATCH 05/10] NCI-Agency/anet#3367: Add custom fields to Organization (client side) --- anet-dictionary.yml | 78 +++++++++++++++++++ .../MultiTypeAdvancedSelectComponent.js | 2 +- client/src/models/Organization.js | 14 +++- client/src/pages/organizations/Edit.js | 16 +++- client/src/pages/organizations/Form.js | 25 +++++- client/src/pages/organizations/New.js | 7 +- client/src/pages/organizations/Show.js | 27 +++++-- src/main/resources/anet-schema.yml | 4 + 8 files changed, 158 insertions(+), 15 deletions(-) diff --git a/anet-dictionary.yml b/anet-dictionary.yml index bc5f5dae0f..62977ce6eb 100644 --- a/anet-dictionary.yml +++ b/anet-dictionary.yml @@ -561,6 +561,84 @@ fields: organization: shortName: Name parentOrg: Parent Organization + customFields: + multipleButtons: + type: enumset + label: Choose one or more of the options + helpText: Help text for choosing multiple values + choices: + opt1: + label: Option 1 + opt2: + label: Option 2 + opt3: + label: Option 3 + inputFieldName: + type: text + label: Text field + placeholder: Placeholder text for input field + helpText: Help text for text field + colourOptions: + type: enum + label: Choose one of the colours + helpText: Help text for choosing colours + choices: + GREEN: + label: Green + color: '#c2ffb3' + AMBER: + label: Amber + color: '#ffe396' + RED: + label: Red + color: '#ff8279' + textareaFieldName: + type: text + label: Textarea field + placeholder: Placeholder text for textarea field + helpText: Help text for textarea field + componentClass: textarea + style: + height: 200px + visibleWhen: $[?(@.colourOptions === 'GREEN')] + numberFieldName: + type: number + typeError: Number field must be a number + label: Number field + placeholder: Placeholder text for number field + helpText: Help text for number field + validations: + - type: integer + - type: min + params: [5] + - type: max + params: [100] + visibleWhen: $[?((@.colourOptions === 'GREEN')||(@.colourOptions === 'RED'))] + nlt: + type: date + label: Not later than date + helpText: Help text for date field + nlt_dt: + type: datetime + label: Not later than datetime + helpText: Help text for datetime field + arrayFieldName: + type: array_of_objects + label: Array of objects + helpText: Here you can add as many objects as needed + addButtonLabel: Add an object + objectLabel: Object + objectFields: + textF: + type: text + label: Object text + placeholder: Placeholder text for object text field + helpText: Help text for object text field + dateF: + type: date + label: Object date + helpText: Help text for object date field + visibleWhen: $[?(@.colourOptions === 'GREEN')] advisor: diff --git a/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js b/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js index ae5beeb96a..e2747e3cf1 100644 --- a/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js +++ b/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js @@ -74,7 +74,7 @@ const widgetPropsOrganization = { overlayColumns: ["Name"], filterDefs: entityFilters, queryParams: { status: Model.STATUS.ACTIVE }, - fields: Models.Organization.autocompleteQuery, + fields: Models.Organization.autocompleteQueryWithNotes, addon: ORGANIZATIONS_ICON } diff --git a/client/src/models/Organization.js b/client/src/models/Organization.js index 8255246e24..2872bc1787 100644 --- a/client/src/models/Organization.js +++ b/client/src/models/Organization.js @@ -1,4 +1,7 @@ -import Model from "components/Model" +import Model, { + createCustomFieldsSchema, + GRAPHQL_NOTES_FIELDS +} from "components/Model" import ORGANIZATIONS_ICON from "resources/organizations.png" import Settings from "settings" import utils from "utils" @@ -20,6 +23,11 @@ export default class Organization extends Model { REPORT_APPROVAL: "REPORT_APPROVAL" } + // create yup schema for the customFields, based on the customFields config + static customFieldsSchema = createCustomFieldsSchema( + Settings.fields.organization.customFields + ) + static yupSchema = yup .object() .shape({ @@ -89,11 +97,15 @@ export default class Organization extends Model { positions: yup.array().nullable().default([]), tasks: yup.array().nullable().default([]) }) + // not actually in the database, the database contains the JSON customFields + .concat(Organization.customFieldsSchema) .concat(Model.yupSchema) static autocompleteQuery = "uuid, shortName, longName, identificationCode, type" + static autocompleteQueryWithNotes = `${this.autocompleteQuery} ${GRAPHQL_NOTES_FIELDS}` + static humanNameOfStatus(status) { return utils.sentenceCase(status) } diff --git a/client/src/pages/organizations/Edit.js b/client/src/pages/organizations/Edit.js index c29b0a647c..c7fb4b6929 100644 --- a/client/src/pages/organizations/Edit.js +++ b/client/src/pages/organizations/Edit.js @@ -1,9 +1,11 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" +import { initInvisibleFields } from "components/CustomFields" +import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import { - PageDispatchersPropType, mapPageDispatchersToProps, + PageDispatchersPropType, useBoilerplate } from "components/Page" import RelatedObjectNotes, { @@ -13,6 +15,8 @@ import { Organization } from "models" import React from "react" import { connect } from "react-redux" import { useParams } from "react-router-dom" +import Settings from "settings" +import utils from "utils" import OrganizationForm from "./Form" const GQL_GET_ORGANIZATION = gql` @@ -65,6 +69,7 @@ const GQL_GET_ORGANIZATION = gql` shortName longName } + customFields ${GRAPHQL_NOTES_FIELDS} } } @@ -87,9 +92,14 @@ const OrganizationEdit = ({ pageDispatchers }) => { if (done) { return result } - + if (data) { + data.organization[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( + data.organization.customFields + ) + } const organization = new Organization(data ? data.organization : {}) - + // mutates the object + initInvisibleFields(organization, Settings.fields.organization.customFields) return (
{ setFieldValue, setFieldTouched, values, + validateForm, submitForm }) => { const isAdmin = currentUser && currentUser.isAdmin() @@ -349,6 +354,19 @@ const OrganizationForm = ({ edit, title, initialValues }) => { )}
)} + {Settings.fields.organization.customFields && ( +
+ +
+ )}
@@ -413,11 +431,14 @@ const OrganizationForm = ({ edit, title, initialValues }) => { "notes", "childrenOrgs", "positions", - "tasks" + "tasks", + "customFields", // initial JSON from the db + DEFAULT_CUSTOM_FIELDS_PARENT ) // strip tasks fields not in data model organization.tasks = values.tasks.map(t => utils.getReference(t)) organization.parentOrg = utils.getReference(organization.parentOrg) + organization.customFields = customFieldsJSONString(values) return API.mutation( edit ? GQL_UPDATE_ORGANIZATION : GQL_CREATE_ORGANIZATION, { organization } diff --git a/client/src/pages/organizations/New.js b/client/src/pages/organizations/New.js index dc1acac6f8..4fea87b745 100644 --- a/client/src/pages/organizations/New.js +++ b/client/src/pages/organizations/New.js @@ -1,9 +1,10 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" +import { initInvisibleFields } from "components/CustomFields" import { - PageDispatchersPropType, mapPageDispatchersToProps, + PageDispatchersPropType, useBoilerplate } from "components/Page" import { Organization } from "models" @@ -11,6 +12,7 @@ import PropTypes from "prop-types" import React from "react" import { connect } from "react-redux" import { useLocation } from "react-router-dom" +import Settings from "settings" import utils from "utils" import OrganizationForm from "./Form" @@ -87,7 +89,8 @@ const OrganizationNewConditional = ({ organization.parentOrg = new Organization(data.organization) organization.type = organization.parentOrg.type } - + // mutates the object + initInvisibleFields(organization, Settings.fields.organization.customFields) return ( { if (done) { return result } - + if (data) { + data.organization[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( + data.organization.customFields + ) + } const organization = new Organization(data ? data.organization : {}) const stateSuccess = routerLocation.state && routerLocation.state.success const stateError = routerLocation.state && routerLocation.state.error @@ -419,6 +426,14 @@ const OrganizationShow = ({ pageDispatchers }) => { } /> + {Settings.fields.organization.customFields && ( +
+ +
+ )}
) diff --git a/src/main/resources/anet-schema.yml b/src/main/resources/anet-schema.yml index c3896c719c..2f2bb71f29 100644 --- a/src/main/resources/anet-schema.yml +++ b/src/main/resources/anet-schema.yml @@ -551,6 +551,10 @@ properties: type: string title: The label for an organization's parent organization description: Used in the UI where an organization's parent organization is shown. + customFields: + type: object + additionalProperties: + "$ref": "#/$defs/customField" advisor: type: object From f6a89f807c3ab40edcaa35cada9a180b56c8baaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Wed, 23 Dec 2020 16:58:58 +0300 Subject: [PATCH 06/10] NCI-Agency/anet#3367: Add custom fields to Location (server side) --- src/main/java/mil/dds/anet/beans/Location.java | 9 +++++---- src/main/java/mil/dds/anet/database/LocationDao.java | 6 +++--- .../java/mil/dds/anet/resources/LocationResource.java | 2 ++ src/main/resources/migrations.xml | 5 +++++ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/mil/dds/anet/beans/Location.java b/src/main/java/mil/dds/anet/beans/Location.java index b24c18881d..a643aba376 100644 --- a/src/main/java/mil/dds/anet/beans/Location.java +++ b/src/main/java/mil/dds/anet/beans/Location.java @@ -9,9 +9,9 @@ import java.util.concurrent.CompletableFuture; import mil.dds.anet.AnetObjectEngine; import mil.dds.anet.utils.Utils; -import mil.dds.anet.views.AbstractAnetBean; +import mil.dds.anet.views.AbstractCustomizableAnetBean; -public class Location extends AbstractAnetBean implements RelatableObject, WithStatus { +public class Location extends AbstractCustomizableAnetBean implements RelatableObject, WithStatus { /** Pseudo uuid to represent 'no location'. */ public static final String DUMMY_LOCATION_UUID = "-1"; @@ -120,12 +120,13 @@ public boolean equals(Object o) { Location l = (Location) o; return Objects.equals(l.getUuid(), uuid) && Objects.equals(l.getName(), name) && Objects.equals(l.getStatus(), status) && Objects.equals(l.getLat(), lat) - && Objects.equals(l.getLng(), lng) && Objects.equals(l.getCreatedAt(), createdAt); + && Objects.equals(l.getLng(), lng) && Objects.equals(l.getCreatedAt(), createdAt) + && Objects.equals(l.getCustomFields(), customFields); } @Override public int hashCode() { - return Objects.hash(uuid, name, status, lat, lng, createdAt); + return Objects.hash(uuid, name, status, lat, lng, createdAt, customFields); } @Override diff --git a/src/main/java/mil/dds/anet/database/LocationDao.java b/src/main/java/mil/dds/anet/database/LocationDao.java index 0586629cb1..c1d9aff3b1 100644 --- a/src/main/java/mil/dds/anet/database/LocationDao.java +++ b/src/main/java/mil/dds/anet/database/LocationDao.java @@ -37,8 +37,8 @@ public List getByIds(List uuids) { @Override public Location insertInternal(Location l) { getDbHandle().createUpdate( - "/* locationInsert */ INSERT INTO locations (uuid, name, status, lat, lng, \"createdAt\", \"updatedAt\") " - + "VALUES (:uuid, :name, :status, :lat, :lng, :createdAt, :updatedAt)") + "/* locationInsert */ INSERT INTO locations (uuid, name, status, lat, lng, \"createdAt\", \"updatedAt\", \"customFields\") " + + "VALUES (:uuid, :name, :status, :lat, :lng, :createdAt, :updatedAt, :customFields)") .bindBean(l).bind("createdAt", DaoUtils.asLocalDateTime(l.getCreatedAt())) .bind("updatedAt", DaoUtils.asLocalDateTime(l.getUpdatedAt())) .bind("status", DaoUtils.getEnumId(l.getStatus())).execute(); @@ -48,7 +48,7 @@ public Location insertInternal(Location l) { @Override public int updateInternal(Location l) { return getDbHandle().createUpdate("/* updateLocation */ UPDATE locations " - + "SET name = :name, status = :status, lat = :lat, lng = :lng, \"updatedAt\" = :updatedAt WHERE uuid = :uuid") + + "SET name = :name, status = :status, lat = :lat, lng = :lng, \"updatedAt\" = :updatedAt, \"customFields\" = :customFields WHERE uuid = :uuid") .bindBean(l).bind("updatedAt", DaoUtils.asLocalDateTime(l.getUpdatedAt())) .bind("status", DaoUtils.getEnumId(l.getStatus())).execute(); } diff --git a/src/main/java/mil/dds/anet/resources/LocationResource.java b/src/main/java/mil/dds/anet/resources/LocationResource.java index e5809132e5..5ff1f29568 100644 --- a/src/main/java/mil/dds/anet/resources/LocationResource.java +++ b/src/main/java/mil/dds/anet/resources/LocationResource.java @@ -49,6 +49,7 @@ public AnetBeanList search(@GraphQLRootContext Map con @GraphQLMutation(name = "createLocation") public Location createLocation(@GraphQLRootContext Map context, @GraphQLArgument(name = "location") Location l) { + l.checkAndFixCustomFields(); final Person user = DaoUtils.getUserFromContext(context); AuthUtils.assertSuperUser(user); if (l.getName() == null || l.getName().trim().length() == 0) { @@ -79,6 +80,7 @@ public Location createLocation(@GraphQLRootContext Map context, @GraphQLMutation(name = "updateLocation") public Integer updateLocation(@GraphQLRootContext Map context, @GraphQLArgument(name = "location") Location l) { + l.checkAndFixCustomFields(); final Person user = DaoUtils.getUserFromContext(context); AuthUtils.assertSuperUser(user); final int numRows = dao.update(l); diff --git a/src/main/resources/migrations.xml b/src/main/resources/migrations.xml index 1911f5ff97..10b72f2c91 100644 --- a/src/main/resources/migrations.xml +++ b/src/main/resources/migrations.xml @@ -3650,4 +3650,9 @@ + + + + + From d7b3a0fc7bbf581e7fc3f972a62dc66ec5d44305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cemalettin=20Ta=C5=9F?= Date: Wed, 23 Dec 2020 17:11:31 +0300 Subject: [PATCH 07/10] NCI-Agency/anet#3367: Add custom fields to Location (client side) --- anet-dictionary.yml | 78 +++++++++++++++++++ .../MultiTypeAdvancedSelectComponent.js | 2 +- client/src/models/Location.js | 14 +++- client/src/pages/locations/Edit.js | 17 +++- client/src/pages/locations/Form.js | 27 ++++++- client/src/pages/locations/New.js | 7 +- client/src/pages/locations/Show.js | 19 ++++- src/main/resources/anet-schema.yml | 4 + 8 files changed, 157 insertions(+), 11 deletions(-) diff --git a/anet-dictionary.yml b/anet-dictionary.yml index 62977ce6eb..945ebae7af 100644 --- a/anet-dictionary.yml +++ b/anet-dictionary.yml @@ -476,6 +476,84 @@ fields: location: format: LAT_LON + customFields: + multipleButtons: + type: enumset + label: Choose one or more of the options + helpText: Help text for choosing multiple values + choices: + opt1: + label: Option 1 + opt2: + label: Option 2 + opt3: + label: Option 3 + inputFieldName: + type: text + label: Text field + placeholder: Placeholder text for input field + helpText: Help text for text field + colourOptions: + type: enum + label: Choose one of the colours + helpText: Help text for choosing colours + choices: + GREEN: + label: Green + color: '#c2ffb3' + AMBER: + label: Amber + color: '#ffe396' + RED: + label: Red + color: '#ff8279' + textareaFieldName: + type: text + label: Textarea field + placeholder: Placeholder text for textarea field + helpText: Help text for textarea field + componentClass: textarea + style: + height: 200px + visibleWhen: $[?(@.colourOptions === 'GREEN')] + numberFieldName: + type: number + typeError: Number field must be a number + label: Number field + placeholder: Placeholder text for number field + helpText: Help text for number field + validations: + - type: integer + - type: min + params: [5] + - type: max + params: [100] + visibleWhen: $[?((@.colourOptions === 'GREEN')||(@.colourOptions === 'RED'))] + nlt: + type: date + label: Not later than date + helpText: Help text for date field + nlt_dt: + type: datetime + label: Not later than datetime + helpText: Help text for datetime field + arrayFieldName: + type: array_of_objects + label: Array of objects + helpText: Here you can add as many objects as needed + addButtonLabel: Add an object + objectLabel: Object + objectFields: + textF: + type: text + label: Object text + placeholder: Placeholder text for object text field + helpText: Help text for object text field + dateF: + type: date + label: Object date + helpText: Help text for object date field + visibleWhen: $[?(@.colourOptions === 'GREEN')] position: name: 'Position Name' diff --git a/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js b/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js index e2747e3cf1..6ce20ebec6 100644 --- a/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js +++ b/client/src/components/advancedSelectWidget/MultiTypeAdvancedSelectComponent.js @@ -94,7 +94,7 @@ const widgetPropsLocation = { overlayColumns: ["Name"], filterDefs: entityFilters, queryParams: { status: Model.STATUS.ACTIVE }, - fields: Models.Location.autocompleteQuery, + fields: Models.Location.autocompleteQueryWithNotes, addon: LOCATIONS_ICON } diff --git a/client/src/models/Location.js b/client/src/models/Location.js index 47361adf1f..98bce1ee4c 100644 --- a/client/src/models/Location.js +++ b/client/src/models/Location.js @@ -1,4 +1,7 @@ -import Model from "components/Model" +import Model, { + createCustomFieldsSchema, + GRAPHQL_NOTES_FIELDS +} from "components/Model" import { convertLatLngToMGRS, convertMGRSToLatLng } from "geoUtils" import _isEmpty from "lodash/isEmpty" import LOCATIONS_ICON from "resources/locations.png" @@ -17,6 +20,11 @@ export default class Location extends Model { REPORT_APPROVAL: "REPORT_APPROVAL" } + // create yup schema for the customFields, based on the customFields config + static customFieldsSchema = createCustomFieldsSchema( + Settings.fields.location.customFields + ) + static yupSchema = yup .object() .shape({ @@ -118,10 +126,14 @@ export default class Location extends Model { .nullable() .default([]) }) + // not actually in the database, the database contains the JSON customFields + .concat(Location.customFieldsSchema) .concat(Model.yupSchema) static autocompleteQuery = "uuid, name" + static autocompleteQueryWithNotes = `${this.autocompleteQuery} ${GRAPHQL_NOTES_FIELDS}` + static hasCoordinates(location) { return ( location && diff --git a/client/src/pages/locations/Edit.js b/client/src/pages/locations/Edit.js index d65e4052bb..701c70a384 100644 --- a/client/src/pages/locations/Edit.js +++ b/client/src/pages/locations/Edit.js @@ -1,9 +1,11 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" import API from "api" import { gql } from "apollo-boost" +import { initInvisibleFields } from "components/CustomFields" +import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import { - PageDispatchersPropType, mapPageDispatchersToProps, + PageDispatchersPropType, useBoilerplate } from "components/Page" import RelatedObjectNotes, { @@ -13,6 +15,8 @@ import { Location } from "models" import React from "react" import { connect } from "react-redux" import { useParams } from "react-router-dom" +import Settings from "settings" +import utils from "utils" import LocationForm from "./Form" const GQL_GET_LOCATION = gql` @@ -52,7 +56,8 @@ const GQL_GET_LOCATION = gql` avatar(size: 32) } } - } + } + customFields ${GRAPHQL_NOTES_FIELDS} } } @@ -75,8 +80,14 @@ const LocationEdit = ({ pageDispatchers }) => { if (done) { return result } - + if (data) { + data.location[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( + data.location.customFields + ) + } const location = new Location(data ? data.location : {}) + // mutates the object + initInvisibleFields(location, Settings.fields.location.customFields) return (
diff --git a/client/src/pages/locations/Form.js b/client/src/pages/locations/Form.js index 8eeada0f6c..568b2b1933 100644 --- a/client/src/pages/locations/Form.js +++ b/client/src/pages/locations/Form.js @@ -2,11 +2,15 @@ import API from "api" import { gql } from "apollo-boost" import AppContext from "components/AppContext" import ApprovalsDefinition from "components/approvals/ApprovalsDefinition" +import { + CustomFieldsContainer, + customFieldsJSONString +} from "components/CustomFields" import * as FieldHelper from "components/FieldHelper" import Fieldset from "components/Fieldset" import Leaflet from "components/Leaflet" import Messages from "components/Messages" -import Model from "components/Model" +import Model, { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import NavigationWarning from "components/NavigationWarning" import { jumpToTop } from "components/Page" import { FastField, Form, Formik } from "formik" @@ -17,6 +21,7 @@ import PropTypes from "prop-types" import React, { useContext, useState } from "react" import { Button } from "react-bootstrap" import { useHistory } from "react-router-dom" +import Settings from "settings" import GeoLocation from "./GeoLocation" const GQL_CREATE_LOCATION = gql` @@ -94,6 +99,7 @@ const LocationForm = ({ edit, title, initialValues }) => { setFieldValue, setValues, values, + validateForm, submitForm }) => { const marker = { @@ -189,7 +195,19 @@ const LocationForm = ({ edit, title, initialValues }) => { setFieldValue={setFieldValue} approversFilters={approversFilters} /> - + {Settings.fields.location.customFields && ( +
+ +
+ )}
@@ -260,8 +278,11 @@ const LocationForm = ({ edit, title, initialValues }) => { const location = Object.without( new Location(values), "notes", - "displayedCoordinate" + "displayedCoordinate", + "customFields", // initial JSON from the db + DEFAULT_CUSTOM_FIELDS_PARENT ) + location.customFields = customFieldsJSONString(values) return API.mutation(edit ? GQL_UPDATE_LOCATION : GQL_CREATE_LOCATION, { location }) diff --git a/client/src/pages/locations/New.js b/client/src/pages/locations/New.js index 45adf8e392..784e860256 100644 --- a/client/src/pages/locations/New.js +++ b/client/src/pages/locations/New.js @@ -1,12 +1,14 @@ import { DEFAULT_SEARCH_PROPS, PAGE_PROPS_NO_NAV } from "actions" +import { initInvisibleFields } from "components/CustomFields" import { - PageDispatchersPropType, mapPageDispatchersToProps, + PageDispatchersPropType, useBoilerplate } from "components/Page" import { Location } from "models" import React from "react" import { connect } from "react-redux" +import Settings from "settings" import LocationForm from "./Form" const LocationNew = ({ pageDispatchers }) => { @@ -17,7 +19,8 @@ const LocationNew = ({ pageDispatchers }) => { }) const location = new Location() - + // mutates the object + initInvisibleFields(location, Settings.fields.location.customFields) return } diff --git a/client/src/pages/locations/Show.js b/client/src/pages/locations/Show.js index ed79a1edaa..5e03fbedd7 100644 --- a/client/src/pages/locations/Show.js +++ b/client/src/pages/locations/Show.js @@ -3,11 +3,13 @@ import API from "api" import { gql } from "apollo-boost" import AppContext from "components/AppContext" import Approvals from "components/approvals/Approvals" +import { ReadonlyCustomFields } from "components/CustomFields" import * as FieldHelper from "components/FieldHelper" import Fieldset from "components/Fieldset" import Leaflet from "components/Leaflet" import LinkTo from "components/LinkTo" import Messages from "components/Messages" +import { DEFAULT_CUSTOM_FIELDS_PARENT } from "components/Model" import { mapPageDispatchersToProps, PageDispatchersPropType, @@ -24,6 +26,8 @@ import { Location } from "models" import React, { useContext } from "react" import { connect } from "react-redux" import { useLocation, useParams } from "react-router-dom" +import Settings from "settings" +import utils from "utils" import GeoLocation, { GEO_LOCATION_DISPLAY_TYPE } from "./GeoLocation" const GQL_GET_LOCATION = gql` @@ -64,6 +68,7 @@ const GQL_GET_LOCATION = gql` } } } + customFields ${GRAPHQL_NOTES_FIELDS} } } @@ -88,7 +93,11 @@ const LocationShow = ({ pageDispatchers }) => { if (done) { return result } - + if (data) { + data.location[DEFAULT_CUSTOM_FIELDS_PARENT] = utils.parseJsonSafe( + data.location.customFields + ) + } const location = new Location(data ? data.location : {}) const stateSuccess = routerLocation.state && routerLocation.state.success const stateError = routerLocation.state && routerLocation.state.error @@ -156,6 +165,14 @@ const LocationShow = ({ pageDispatchers }) => { + {Settings.fields.location.customFields && ( +
+ +
+ )} diff --git a/src/main/resources/anet-schema.yml b/src/main/resources/anet-schema.yml index 2f2bb71f29..63e69de990 100644 --- a/src/main/resources/anet-schema.yml +++ b/src/main/resources/anet-schema.yml @@ -523,6 +523,10 @@ properties: enum: [LAT_LON, MGRS] title: Coordinate format for location description: Used in the UI where a location's coordinate is shown. Defaults to LAT_LON. + customFields: + type: object + additionalProperties: + "$ref": "#/$defs/customField" position: type: object From d1fb47b0126498cfd4df642857d1c6cacbcaca0b Mon Sep 17 00:00:00 2001 From: Gertjan van Oosten Date: Mon, 4 Jan 2021 13:25:28 +0100 Subject: [PATCH 08/10] NCI-Agency/anet#3367: Update mapper to include customFields --- src/main/java/mil/dds/anet/database/mappers/LocationMapper.java | 2 +- .../java/mil/dds/anet/database/mappers/OrganizationMapper.java | 2 +- src/main/java/mil/dds/anet/database/mappers/PositionMapper.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/mil/dds/anet/database/mappers/LocationMapper.java b/src/main/java/mil/dds/anet/database/mappers/LocationMapper.java index 4dcd5d6170..c411806996 100644 --- a/src/main/java/mil/dds/anet/database/mappers/LocationMapper.java +++ b/src/main/java/mil/dds/anet/database/mappers/LocationMapper.java @@ -11,7 +11,7 @@ public class LocationMapper implements RowMapper { @Override public Location map(ResultSet rs, StatementContext ctx) throws SQLException { Location l = new Location(); - MapperUtils.setCommonBeanFields(l, rs, null); + MapperUtils.setCustomizableBeanFields(l, rs, null); l.setName(rs.getString("name")); l.setStatus(MapperUtils.getEnumIdx(rs, "status", Location.Status.class)); // preserve NULL values; when NULL there are no coordinates set: diff --git a/src/main/java/mil/dds/anet/database/mappers/OrganizationMapper.java b/src/main/java/mil/dds/anet/database/mappers/OrganizationMapper.java index 7f0bf126ed..7aec23039c 100644 --- a/src/main/java/mil/dds/anet/database/mappers/OrganizationMapper.java +++ b/src/main/java/mil/dds/anet/database/mappers/OrganizationMapper.java @@ -12,7 +12,7 @@ public class OrganizationMapper implements RowMapper { @Override public Organization map(ResultSet r, StatementContext ctx) throws SQLException { Organization org = new Organization(); - MapperUtils.setCommonBeanFields(org, r, "organizations"); + MapperUtils.setCustomizableBeanFields(org, r, "organizations"); org.setShortName(r.getString("organizations_shortName")); org.setLongName(r.getString("organizations_longName")); org.setStatus(MapperUtils.getEnumIdx(r, "organizations_status", Organization.Status.class)); diff --git a/src/main/java/mil/dds/anet/database/mappers/PositionMapper.java b/src/main/java/mil/dds/anet/database/mappers/PositionMapper.java index 7159faa0be..d52d03f69d 100644 --- a/src/main/java/mil/dds/anet/database/mappers/PositionMapper.java +++ b/src/main/java/mil/dds/anet/database/mappers/PositionMapper.java @@ -25,7 +25,7 @@ public Position map(ResultSet rs, StatementContext ctx) throws SQLException { } public static Position fillInFields(Position p, ResultSet rs) throws SQLException { - MapperUtils.setCommonBeanFields(p, rs, "positions"); + MapperUtils.setCustomizableBeanFields(p, rs, "positions"); p.setName(rs.getString("positions_name")); p.setCode(rs.getString("positions_code")); p.setType(MapperUtils.getEnumIdx(rs, "positions_type", PositionType.class)); From 32f754c2a9857fa36cc526e2e8476b326f39451a Mon Sep 17 00:00:00 2001 From: Gertjan van Oosten Date: Mon, 4 Jan 2021 13:25:56 +0100 Subject: [PATCH 09/10] NCI-Agency/anet#3367: Refactor hashCode/equals to use superclass --- src/main/java/mil/dds/anet/beans/Location.java | 12 ++++++------ .../java/mil/dds/anet/beans/Organization.java | 12 ++++++------ src/main/java/mil/dds/anet/beans/Person.java | 15 +++++++-------- src/main/java/mil/dds/anet/beans/Position.java | 13 ++++++------- src/main/java/mil/dds/anet/beans/Report.java | 16 ++++++++-------- src/main/java/mil/dds/anet/beans/Task.java | 11 ++++++----- .../anet/views/AbstractCustomizableAnetBean.java | 16 +++++++++++++++- 7 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/main/java/mil/dds/anet/beans/Location.java b/src/main/java/mil/dds/anet/beans/Location.java index a643aba376..44b82f571e 100644 --- a/src/main/java/mil/dds/anet/beans/Location.java +++ b/src/main/java/mil/dds/anet/beans/Location.java @@ -117,16 +117,16 @@ public boolean equals(Object o) { if (!(o instanceof Location)) { return false; } - Location l = (Location) o; - return Objects.equals(l.getUuid(), uuid) && Objects.equals(l.getName(), name) - && Objects.equals(l.getStatus(), status) && Objects.equals(l.getLat(), lat) - && Objects.equals(l.getLng(), lng) && Objects.equals(l.getCreatedAt(), createdAt) - && Objects.equals(l.getCustomFields(), customFields); + final Location other = (Location) o; + return super.equals(o) && Objects.equals(other.getUuid(), uuid) + && Objects.equals(other.getName(), name) && Objects.equals(other.getStatus(), status) + && Objects.equals(other.getLat(), lat) && Objects.equals(other.getLng(), lng) + && Objects.equals(other.getCreatedAt(), createdAt); } @Override public int hashCode() { - return Objects.hash(uuid, name, status, lat, lng, createdAt, customFields); + return Objects.hash(super.hashCode(), uuid, name, status, lat, lng, createdAt); } @Override diff --git a/src/main/java/mil/dds/anet/beans/Organization.java b/src/main/java/mil/dds/anet/beans/Organization.java index e03ac02a69..8f7536c547 100644 --- a/src/main/java/mil/dds/anet/beans/Organization.java +++ b/src/main/java/mil/dds/anet/beans/Organization.java @@ -262,19 +262,19 @@ public boolean equals(Object o) { if (!(o instanceof Organization)) { return false; } - Organization other = (Organization) o; - return Objects.equals(other.getUuid(), uuid) && Objects.equals(other.getShortName(), shortName) + final Organization other = (Organization) o; + return super.equals(o) && Objects.equals(other.getUuid(), uuid) + && Objects.equals(other.getShortName(), shortName) && Objects.equals(other.getLongName(), longName) && Objects.equals(other.getStatus(), status) && Objects.equals(other.getIdentificationCode(), identificationCode) - && Objects.equals(other.getType(), type) - && Objects.equals(other.getCustomFields(), customFields); + && Objects.equals(other.getType(), type); } @Override public int hashCode() { - return Objects.hash(uuid, shortName, longName, status, identificationCode, type, createdAt, - updatedAt, customFields); + return Objects.hash(super.hashCode(), uuid, shortName, longName, status, identificationCode, + type, createdAt, updatedAt); } @Override diff --git a/src/main/java/mil/dds/anet/beans/Person.java b/src/main/java/mil/dds/anet/beans/Person.java index 1e1def3339..87caa47bde 100644 --- a/src/main/java/mil/dds/anet/beans/Person.java +++ b/src/main/java/mil/dds/anet/beans/Person.java @@ -324,9 +324,10 @@ public boolean equals(Object o) { if (!(o instanceof Person)) { return false; } - Person other = (Person) o; - boolean b = Objects.equals(uuid, other.getUuid()) && Objects.equals(other.getName(), name) - && Objects.equals(other.getStatus(), status) && Objects.equals(other.getRole(), role) + final Person other = (Person) o; + return super.equals(o) && Objects.equals(uuid, other.getUuid()) + && Objects.equals(other.getName(), name) && Objects.equals(other.getStatus(), status) + && Objects.equals(other.getRole(), role) && Objects.equals(other.getEmailAddress(), emailAddress) && Objects.equals(other.getPhoneNumber(), phoneNumber) && Objects.equals(other.getRank(), rank) && Objects.equals(other.getBiography(), biography) @@ -336,15 +337,13 @@ public boolean equals(Object o) { && (createdAt != null ? createdAt.equals(other.getCreatedAt()) : (other.getCreatedAt() == null && updatedAt != null) ? updatedAt.equals(other.getUpdatedAt()) - : other.getUpdatedAt() == null) - && Objects.equals(other.getCustomFields(), customFields); - return b; + : other.getUpdatedAt() == null); } @Override public int hashCode() { - return Objects.hash(uuid, name, status, role, emailAddress, phoneNumber, rank, biography, - pendingVerification, avatar, code, createdAt, updatedAt, customFields); + return Objects.hash(super.hashCode(), uuid, name, status, role, emailAddress, phoneNumber, rank, + biography, pendingVerification, avatar, code, createdAt, updatedAt); } @Override diff --git a/src/main/java/mil/dds/anet/beans/Position.java b/src/main/java/mil/dds/anet/beans/Position.java index 03ca85fbdb..d33c24d3d5 100644 --- a/src/main/java/mil/dds/anet/beans/Position.java +++ b/src/main/java/mil/dds/anet/beans/Position.java @@ -268,17 +268,16 @@ public boolean equals(Object o) { if (!(o instanceof Position)) { return false; } - Position other = (Position) o; - return Objects.equals(uuid, other.getUuid()) && Objects.equals(name, other.getName()) - && Objects.equals(code, other.getCode()) && Objects.equals(type, other.getType()) - && Objects.equals(status, other.getStatus()) - && Objects.equals(getOrganizationUuid(), other.getOrganizationUuid()) - && Objects.equals(customFields, other.getCustomFields()); + final Position other = (Position) o; + return super.equals(o) && Objects.equals(uuid, other.getUuid()) + && Objects.equals(name, other.getName()) && Objects.equals(code, other.getCode()) + && Objects.equals(type, other.getType()) && Objects.equals(status, other.getStatus()) + && Objects.equals(getOrganizationUuid(), other.getOrganizationUuid()); } @Override public int hashCode() { - return Objects.hash(uuid, name, code, type, status, organization, customFields); + return Objects.hash(super.hashCode(), uuid, name, code, type, status, organization); } @Override diff --git a/src/main/java/mil/dds/anet/beans/Report.java b/src/main/java/mil/dds/anet/beans/Report.java index 3aa610e0a6..0d9245cc50 100644 --- a/src/main/java/mil/dds/anet/beans/Report.java +++ b/src/main/java/mil/dds/anet/beans/Report.java @@ -823,8 +823,9 @@ public boolean equals(Object o) { if (!(o instanceof Report)) { return false; } - Report r = (Report) o; - return Objects.equals(r.getUuid(), uuid) && Objects.equals(r.getState(), state) + final Report r = (Report) o; + return super.equals(o) && Objects.equals(r.getUuid(), uuid) + && Objects.equals(r.getState(), state) && Objects.equals(r.getApprovalStepUuid(), getApprovalStepUuid()) && Objects.equals(r.getCreatedAt(), createdAt) && Objects.equals(r.getUpdatedAt(), updatedAt) @@ -839,16 +840,15 @@ public boolean equals(Object o) { && Objects.equals(r.getNextSteps(), nextSteps) && Objects.equals(r.getComments(), comments) && Objects.equals(r.getTags(), tags) && Objects.equals(r.getReportSensitiveInformation(), reportSensitiveInformation) - && Objects.equals(r.getAuthorizationGroups(), authorizationGroups) - && Objects.equals(r.getCustomFields(), customFields); + && Objects.equals(r.getAuthorizationGroups(), authorizationGroups); } @Override public int hashCode() { - return Objects.hash(uuid, state, approvalStep, createdAt, updatedAt, location, intent, exsum, - reportPeople, tasks, reportText, nextSteps, comments, atmosphere, atmosphereDetails, - engagementDate, duration, tags, reportSensitiveInformation, authorizationGroups, - customFields); + return Objects.hash(super.hashCode(), uuid, state, approvalStep, createdAt, updatedAt, location, + intent, exsum, reportPeople, tasks, reportText, nextSteps, comments, atmosphere, + atmosphereDetails, engagementDate, duration, tags, reportSensitiveInformation, + authorizationGroups); } public static Report createWithUuid(String uuid) { diff --git a/src/main/java/mil/dds/anet/beans/Task.java b/src/main/java/mil/dds/anet/beans/Task.java index 957a04370a..0fc844e645 100644 --- a/src/main/java/mil/dds/anet/beans/Task.java +++ b/src/main/java/mil/dds/anet/beans/Task.java @@ -275,18 +275,19 @@ public boolean equals(Object o) { if (!(o instanceof Task)) { return false; } - Task other = (Task) o; - return Objects.equals(other.getUuid(), uuid) && Objects.equals(other.getShortName(), shortName) + final Task other = (Task) o; + return super.equals(o) && Objects.equals(other.getUuid(), uuid) + && Objects.equals(other.getShortName(), shortName) && Objects.equals(other.getLongName(), longName) && Objects.equals(other.getCategory(), category) && Objects.equals(other.getCustomFieldRef1Uuid(), getCustomFieldRef1Uuid()) - && Objects.equals(other.getStatus(), status) - && Objects.equals(other.getCustomFields(), customFields); + && Objects.equals(other.getStatus(), status); } @Override public int hashCode() { - return Objects.hash(uuid, shortName, longName, category, customFieldRef1, status, customFields); + return Objects.hash(super.hashCode(), uuid, shortName, longName, category, customFieldRef1, + status); } @Override diff --git a/src/main/java/mil/dds/anet/views/AbstractCustomizableAnetBean.java b/src/main/java/mil/dds/anet/views/AbstractCustomizableAnetBean.java index 6977f96fce..e5f3fef5b8 100644 --- a/src/main/java/mil/dds/anet/views/AbstractCustomizableAnetBean.java +++ b/src/main/java/mil/dds/anet/views/AbstractCustomizableAnetBean.java @@ -4,6 +4,7 @@ import io.leangen.graphql.annotations.GraphQLInputField; import io.leangen.graphql.annotations.GraphQLQuery; import java.lang.invoke.MethodHandles; +import java.util.Objects; import mil.dds.anet.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +16,7 @@ public abstract class AbstractCustomizableAnetBean extends AbstractAnetBean { @GraphQLQuery @GraphQLInputField - protected String customFields; + private String customFields; public String getCustomFields() { return customFields; @@ -32,7 +33,20 @@ public void checkAndFixCustomFields() { setCustomFields(null); logger.error("Unable to process Json, customFields payload discarded", e); } + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof AbstractCustomizableAnetBean)) { + return false; + } + final AbstractCustomizableAnetBean other = (AbstractCustomizableAnetBean) o; + return Objects.equals(customFields, other.getCustomFields()); + } + @Override + public int hashCode() { + return Objects.hash(customFields); } } From 1dc82b10d05f71251259f37d585cc2c531e50694 Mon Sep 17 00:00:00 2001 From: Gertjan van Oosten Date: Mon, 4 Jan 2021 13:26:35 +0100 Subject: [PATCH 10/10] NCI-Agency/anet#3367: Checkstyle: format lines for length --- .../mil/dds/anet/database/LocationDao.java | 10 ++++---- .../dds/anet/database/OrganizationDao.java | 19 ++++++++++----- .../java/mil/dds/anet/database/PersonDao.java | 14 +++++------ .../mil/dds/anet/database/PositionDao.java | 12 ++++++---- .../java/mil/dds/anet/database/ReportDao.java | 24 +++++++++---------- .../java/mil/dds/anet/database/TaskDao.java | 19 ++++++++------- 6 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/main/java/mil/dds/anet/database/LocationDao.java b/src/main/java/mil/dds/anet/database/LocationDao.java index c1d9aff3b1..6efc8694d3 100644 --- a/src/main/java/mil/dds/anet/database/LocationDao.java +++ b/src/main/java/mil/dds/anet/database/LocationDao.java @@ -37,8 +37,9 @@ public List getByIds(List uuids) { @Override public Location insertInternal(Location l) { getDbHandle().createUpdate( - "/* locationInsert */ INSERT INTO locations (uuid, name, status, lat, lng, \"createdAt\", \"updatedAt\", \"customFields\") " - + "VALUES (:uuid, :name, :status, :lat, :lng, :createdAt, :updatedAt, :customFields)") + "/* locationInsert */ INSERT INTO locations (uuid, name, status, lat, lng, \"createdAt\", " + + "\"updatedAt\", \"customFields\") VALUES (:uuid, :name, :status, :lat, :lng, :createdAt, " + + ":updatedAt, :customFields)") .bindBean(l).bind("createdAt", DaoUtils.asLocalDateTime(l.getCreatedAt())) .bind("updatedAt", DaoUtils.asLocalDateTime(l.getUpdatedAt())) .bind("status", DaoUtils.getEnumId(l.getStatus())).execute(); @@ -48,8 +49,9 @@ public Location insertInternal(Location l) { @Override public int updateInternal(Location l) { return getDbHandle().createUpdate("/* updateLocation */ UPDATE locations " - + "SET name = :name, status = :status, lat = :lat, lng = :lng, \"updatedAt\" = :updatedAt, \"customFields\" = :customFields WHERE uuid = :uuid") - .bindBean(l).bind("updatedAt", DaoUtils.asLocalDateTime(l.getUpdatedAt())) + + "SET name = :name, status = :status, lat = :lat, lng = :lng, \"updatedAt\" = :updatedAt, " + + "\"customFields\" = :customFields WHERE uuid = :uuid").bindBean(l) + .bind("updatedAt", DaoUtils.asLocalDateTime(l.getUpdatedAt())) .bind("status", DaoUtils.getEnumId(l.getStatus())).execute(); } diff --git a/src/main/java/mil/dds/anet/database/OrganizationDao.java b/src/main/java/mil/dds/anet/database/OrganizationDao.java index 226800cbba..8f10619058 100644 --- a/src/main/java/mil/dds/anet/database/OrganizationDao.java +++ b/src/main/java/mil/dds/anet/database/OrganizationDao.java @@ -116,9 +116,13 @@ public List getOrgsByShortNames(List shortNames) { @Override public Organization insertInternal(Organization org) { - getDbHandle().createUpdate( - "/* insertOrg */ INSERT INTO organizations (uuid, \"shortName\", \"longName\", status, \"identificationCode\", type, \"createdAt\", \"updatedAt\", \"parentOrgUuid\", \"customFields\") " - + "VALUES (:uuid, :shortName, :longName, :status, :identificationCode, :type, :createdAt, :updatedAt, :parentOrgUuid, :customFields)") + getDbHandle() + .createUpdate( + "/* insertOrg */ INSERT INTO organizations (uuid, \"shortName\", \"longName\", status, " + + "\"identificationCode\", type, \"createdAt\", \"updatedAt\", \"parentOrgUuid\", " + + "\"customFields\") VALUES (:uuid, :shortName, :longName, :status, " + + ":identificationCode, :type, :createdAt, :updatedAt, :parentOrgUuid, " + + ":customFields)") .bindBean(org).bind("createdAt", DaoUtils.asLocalDateTime(org.getCreatedAt())) .bind("updatedAt", DaoUtils.asLocalDateTime(org.getUpdatedAt())) .bind("status", DaoUtils.getEnumId(org.getStatus())) @@ -129,9 +133,12 @@ public Organization insertInternal(Organization org) { @Override public int updateInternal(Organization org) { - return getDbHandle().createUpdate("/* updateOrg */ UPDATE organizations " - + "SET \"shortName\" = :shortName, \"longName\" = :longName, status = :status, \"identificationCode\" = :identificationCode, type = :type, " - + "\"updatedAt\" = :updatedAt, \"parentOrgUuid\" = :parentOrgUuid, \"customFields\" = :customFields where uuid = :uuid") + return getDbHandle() + .createUpdate("/* updateOrg */ UPDATE organizations " + + "SET \"shortName\" = :shortName, \"longName\" = :longName, status = :status, " + + "\"identificationCode\" = :identificationCode, type = :type, " + + "\"updatedAt\" = :updatedAt, \"parentOrgUuid\" = :parentOrgUuid, " + + "\"customFields\" = :customFields WHERE uuid = :uuid") .bindBean(org).bind("updatedAt", DaoUtils.asLocalDateTime(org.getUpdatedAt())) .bind("status", DaoUtils.getEnumId(org.getStatus())) .bind("type", DaoUtils.getEnumId(org.getType())) diff --git a/src/main/java/mil/dds/anet/database/PersonDao.java b/src/main/java/mil/dds/anet/database/PersonDao.java index 792e54acf4..8f75f28f37 100644 --- a/src/main/java/mil/dds/anet/database/PersonDao.java +++ b/src/main/java/mil/dds/anet/database/PersonDao.java @@ -139,10 +139,11 @@ public List> getPersonPositionHistory(List f public Person insertInternal(Person p) { StringBuilder sql = new StringBuilder(); sql.append("/* personInsert */ INSERT INTO people " - + "(uuid, name, status, role, \"emailAddress\", \"phoneNumber\", rank, \"pendingVerification\", " - + "gender, country, avatar, code, \"endOfTourDate\", biography, \"domainUsername\", \"createdAt\", \"updatedAt\", \"customFields\") " - + "VALUES (:uuid, :name, :status, :role, :emailAddress, :phoneNumber, :rank, :pendingVerification, " - + ":gender, :country, :avatar, :code, "); + + "(uuid, name, status, role, \"emailAddress\", \"phoneNumber\", rank, " + + "\"pendingVerification\", gender, country, avatar, code, \"endOfTourDate\", biography, " + + "\"domainUsername\", \"createdAt\", \"updatedAt\", \"customFields\") " + + "VALUES (:uuid, :name, :status, :role, :emailAddress, :phoneNumber, :rank, " + + ":pendingVerification, :gender, :country, :avatar, :code, "); if (DaoUtils.isMsSql()) { // MsSql requires an explicit CAST when datetime2 might be NULL. sql.append("CAST(:endOfTourDate AS datetime2), "); @@ -163,9 +164,8 @@ public Person insertInternal(Person p) { @Override public int updateInternal(Person p) { StringBuilder sql = new StringBuilder("/* personUpdate */ UPDATE people " - + "SET name = :name, status = :status, role = :role, " - + "gender = :gender, country = :country, \"emailAddress\" = :emailAddress, " - + "\"avatar\" = :avatar, code = :code, " + + "SET name = :name, status = :status, role = :role, gender = :gender, country = :country, " + + "\"emailAddress\" = :emailAddress, \"avatar\" = :avatar, code = :code, " + "\"phoneNumber\" = :phoneNumber, rank = :rank, biography = :biography, " + "\"pendingVerification\" = :pendingVerification, \"domainUsername\" = :domainUsername, " + "\"updatedAt\" = :updatedAt, \"customFields\" = :customFields, "); diff --git a/src/main/java/mil/dds/anet/database/PositionDao.java b/src/main/java/mil/dds/anet/database/PositionDao.java index 504b04092e..dffa8630c2 100644 --- a/src/main/java/mil/dds/anet/database/PositionDao.java +++ b/src/main/java/mil/dds/anet/database/PositionDao.java @@ -45,8 +45,9 @@ public Position insertInternal(Position p) { try { getDbHandle() .createUpdate("/* positionInsert */ INSERT INTO positions (uuid, name, code, type, " - + "status, \"organizationUuid\", \"locationUuid\", \"createdAt\", \"updatedAt\", \"customFields\") " - + "VALUES (:uuid, :name, :code, :type, :status, :organizationUuid, :locationUuid, :createdAt, :updatedAt, :customFields)") + + "status, \"organizationUuid\", \"locationUuid\", \"createdAt\", \"updatedAt\", " + + "\"customFields\") VALUES (:uuid, :name, :code, :type, :status, :organizationUuid, " + + ":locationUuid, :createdAt, :updatedAt, :customFields)") .bindBean(p).bind("createdAt", DaoUtils.asLocalDateTime(p.getCreatedAt())) .bind("updatedAt", DaoUtils.asLocalDateTime(p.getUpdatedAt())) .bind("type", DaoUtils.getEnumId(p.getType())) @@ -157,9 +158,10 @@ public int updateInternal(Position p) { try { final int nr = getDbHandle() - .createUpdate("/* positionUpdate */ UPDATE positions SET name = :name, " - + "code = :code, \"organizationUuid\" = :organizationUuid, type = :type, status = :status, " - + "\"locationUuid\" = :locationUuid, \"updatedAt\" = :updatedAt, \"customFields\" = :customFields WHERE uuid = :uuid") + .createUpdate("/* positionUpdate */ UPDATE positions SET name = :name, code = :code, " + + "\"organizationUuid\" = :organizationUuid, type = :type, status = :status, " + + "\"locationUuid\" = :locationUuid, \"updatedAt\" = :updatedAt, " + + "\"customFields\" = :customFields WHERE uuid = :uuid") .bindBean(p).bind("updatedAt", DaoUtils.asLocalDateTime(p.getUpdatedAt())) .bind("type", DaoUtils.getEnumId(p.getType())) .bind("status", DaoUtils.getEnumId(p.getStatus())).execute(); diff --git a/src/main/java/mil/dds/anet/database/ReportDao.java b/src/main/java/mil/dds/anet/database/ReportDao.java index 41a0ea8f29..063979aefb 100644 --- a/src/main/java/mil/dds/anet/database/ReportDao.java +++ b/src/main/java/mil/dds/anet/database/ReportDao.java @@ -123,8 +123,8 @@ public Report insertInternal(Report r, Person user) { } else { sql.append(":engagementDate, :releasedAt, "); } - sql.append( - ":duration, :atmosphere, :cancelledReason, :atmosphereDetails, :advisorOrgUuid, :principalOrgUuid, :customFields)"); + sql.append(":duration, :atmosphere, :cancelledReason, :atmosphereDetails, :advisorOrgUuid, " + + ":principalOrgUuid, :customFields)"); getDbHandle().createUpdate(sql.toString()).bindBean(r) .bind("createdAt", DaoUtils.asLocalDateTime(r.getCreatedAt())) @@ -226,20 +226,20 @@ public int updateInternal(Report r, Person user) { StringBuilder sql = new StringBuilder("/* updateReport */ UPDATE reports SET " + "state = :state, \"updatedAt\" = :updatedAt, \"locationUuid\" = :locationUuid, " - + "intent = :intent, exsum = :exsum, text = :reportText, " - + "\"keyOutcomes\" = :keyOutcomes, \"nextSteps\" = :nextSteps, " - + "\"approvalStepUuid\" = :approvalStepUuid, "); + + "intent = :intent, exsum = :exsum, text = :reportText, \"keyOutcomes\" = :keyOutcomes, " + + "\"nextSteps\" = :nextSteps, \"approvalStepUuid\" = :approvalStepUuid, "); if (DaoUtils.isMsSql()) { - sql.append( - "\"engagementDate\" = CAST(:engagementDate AS datetime2), \"releasedAt\" = CAST(:releasedAt AS datetime2), "); + sql.append("\"engagementDate\" = CAST(:engagementDate AS datetime2), " + + "\"releasedAt\" = CAST(:releasedAt AS datetime2), "); } else { sql.append("\"engagementDate\" = :engagementDate, \"releasedAt\" = :releasedAt, "); } - sql.append( - "duration = :duration, atmosphere = :atmosphere, \"atmosphereDetails\" = :atmosphereDetails, " - + "\"cancelledReason\" = :cancelledReason, " - + "\"principalOrganizationUuid\" = :principalOrgUuid, \"advisorOrganizationUuid\" = :advisorOrgUuid, " - + "\"customFields\" = :customFields " + "WHERE uuid = :uuid"); + sql.append("duration = :duration, atmosphere = :atmosphere, " + + "\"atmosphereDetails\" = :atmosphereDetails, " + + "\"cancelledReason\" = :cancelledReason, " + + "\"principalOrganizationUuid\" = :principalOrgUuid, " + + "\"advisorOrganizationUuid\" = :advisorOrgUuid, " + + "\"customFields\" = :customFields WHERE uuid = :uuid"); return getDbHandle().createUpdate(sql.toString()).bindBean(r) .bind("updatedAt", DaoUtils.asLocalDateTime(r.getUpdatedAt())) diff --git a/src/main/java/mil/dds/anet/database/TaskDao.java b/src/main/java/mil/dds/anet/database/TaskDao.java index 3a1cf77d1d..91ad744bc1 100644 --- a/src/main/java/mil/dds/anet/database/TaskDao.java +++ b/src/main/java/mil/dds/anet/database/TaskDao.java @@ -108,11 +108,13 @@ public CompletableFuture> getTasksBySearch(Map contex @Override public Task insertInternal(Task p) { getDbHandle().createUpdate("/* insertTask */ INSERT INTO tasks " - + "(uuid, \"longName\", \"shortName\", category, \"customFieldRef1Uuid\", \"createdAt\", \"updatedAt\", status, " - + "\"customField\", \"customFieldEnum1\", \"customFieldEnum2\", \"plannedCompletion\", \"projectedCompletion\", \"customFields\") " - + "VALUES (:uuid, :longName, :shortName, :category, :customFieldRef1Uuid, :createdAt, :updatedAt, :status, " - + ":customField, :customFieldEnum1, :customFieldEnum2, :plannedCompletion, :projectedCompletion, :customFields)") - .bindBean(p).bind("createdAt", DaoUtils.asLocalDateTime(p.getCreatedAt())) + + "(uuid, \"longName\", \"shortName\", category, \"customFieldRef1Uuid\", \"createdAt\", " + + "\"updatedAt\", status, \"customField\", \"customFieldEnum1\", \"customFieldEnum2\", " + + "\"plannedCompletion\", \"projectedCompletion\", \"customFields\") " + + "VALUES (:uuid, :longName, :shortName, :category, :customFieldRef1Uuid, :createdAt, " + + ":updatedAt, :status, :customField, :customFieldEnum1, :customFieldEnum2, " + + ":plannedCompletion, :projectedCompletion, :customFields)").bindBean(p) + .bind("createdAt", DaoUtils.asLocalDateTime(p.getCreatedAt())) .bind("updatedAt", DaoUtils.asLocalDateTime(p.getUpdatedAt())) .bind("plannedCompletion", DaoUtils.asLocalDateTime(p.getPlannedCompletion())) .bind("projectedCompletion", DaoUtils.asLocalDateTime(p.getProjectedCompletion())) @@ -141,10 +143,11 @@ void inserttaskTaskedOrganizations(@Bind("taskUuid") String taskUuid, public int updateInternal(Task p) { return getDbHandle().createUpdate( "/* updateTask */ UPDATE tasks set \"longName\" = :longName, \"shortName\" = :shortName, " - + "category = :category, \"customFieldRef1Uuid\" = :customFieldRef1Uuid, \"updatedAt\" = :updatedAt, status = :status, " - + "\"customField\" = :customField, \"customFieldEnum1\" = :customFieldEnum1, \"customFieldEnum2\" = :customFieldEnum2, " + + "category = :category, \"customFieldRef1Uuid\" = :customFieldRef1Uuid, " + + "\"updatedAt\" = :updatedAt, status = :status, \"customField\" = :customField, " + + "\"customFieldEnum1\" = :customFieldEnum1, \"customFieldEnum2\" = :customFieldEnum2, " + "\"plannedCompletion\" = :plannedCompletion, \"projectedCompletion\" = :projectedCompletion, " - + "\"customFields\" = :customFields " + "WHERE uuid = :uuid") + + "\"customFields\" = :customFields WHERE uuid = :uuid") .bindBean(p).bind("updatedAt", DaoUtils.asLocalDateTime(p.getUpdatedAt())) .bind("plannedCompletion", DaoUtils.asLocalDateTime(p.getPlannedCompletion())) .bind("projectedCompletion", DaoUtils.asLocalDateTime(p.getProjectedCompletion()))