From d31fe3696320791a02238a5e27c41855b63a912d Mon Sep 17 00:00:00 2001 From: stdavis Date: Thu, 19 Oct 2023 11:50:47 -0600 Subject: [PATCH] feat: add support for field aliases via config spreadsheet in related table grid fields --- functions/common/config.js | 38 ++++++++++++--- functions/common/config.test.js | 48 +++++++++++++++++++ functions/configs.js | 10 +++- functions/configs.test.js | 5 ++ src/components/Map.jsx | 4 +- src/components/RelatedRecords.jsx | 16 +++---- src/components/ResultTable.jsx | 7 +-- .../search-wizard/QueryLayer.stories.jsx | 1 + .../search-wizard/filters/utils.test.js | 1 + .../SimpleTable.stories.jsx | 22 --------- 10 files changed, 109 insertions(+), 43 deletions(-) create mode 100644 functions/common/config.test.js diff --git a/functions/common/config.js b/functions/common/config.js index 76316259..7506ad28 100644 --- a/functions/common/config.js +++ b/functions/common/config.js @@ -20,6 +20,7 @@ import { array, string } from 'yup'; * 'Map Label Field': string; * 'Sort Field': string; * 'Identify Attributes': string; + * 'Result Grid Fields': (string | { name: string; alias: string })[]; * 'Related Tables': string; * 'Document Search': string; * 'GRAMA Request': string; @@ -31,6 +32,18 @@ import { array, string } from 'yup'; * }} QueryLayerConfig */ +/** + * @typedef {{ + * 'Additional Information': string; + * 'Feature Service': string; + * 'Grid Fields': (string | { name: string; alias: string })[]; + * 'OID Field': string; + * 'SGID Table Name': string; + * 'Table Name': string; + * 'Tab Name': string; + * }} RelatedTableConfig + */ + /** * @typedef {{ * 'Parent Dataset Name': string; @@ -44,8 +57,21 @@ import { array, string } from 'yup'; const urlRegex = /https?:\/\//i; const invalidUrl = '"${value}" must be a valid URL ("{" and "}" are allowed)'; -function commaSeparatedStringToArray(value) { - return value.split(',').map((v) => v.trim()); +export function transformFields(value) { + const entries = value.split(',').map((v) => v.trim()); + + return entries.map((entry) => { + const match = entry.match(/^(.*)\(([^()]*(?:\([^()]*\)[^()]*)*)\)$/); + + if (match) { + return { + name: match[1].trim(), + alias: match[2].trim(), + }; + } + + return entry; + }); } export const fieldConfigs = { @@ -132,8 +158,8 @@ export const fieldConfigs = { }, resultGridFields: { name: 'Result Grid Fields', - schema: array().of(string()).required(), - transform: commaSeparatedStringToArray, + schema: array().required(), + transform: transformFields, }, sgidFeatureClassName: { name: 'SGID Feature Class Name', @@ -167,8 +193,8 @@ export const fieldConfigs = { }, gridFields: { name: 'Grid Fields', - schema: array().of(string()).required(), - transform: commaSeparatedStringToArray, + schema: array().required(), + transform: transformFields, }, oidField: { name: 'OID Field', diff --git a/functions/common/config.test.js b/functions/common/config.test.js new file mode 100644 index 00000000..85df85a3 --- /dev/null +++ b/functions/common/config.test.js @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; +import { transformFields } from './config'; + +describe('transformFields', () => { + it('handles a list of plain fields without aliases', () => { + const config = 'one, two,three'; + + expect(transformFields(config)).toEqual(['one', 'two', 'three']); + }); + + it('handles a list of fields with aliases', () => { + const config = 'one (1), two (2),three (3)'; + + expect(transformFields(config)).toEqual([ + { + name: 'one', + alias: '1', + }, + { + name: 'two', + alias: '2', + }, + { + name: 'three', + alias: '3', + }, + ]); + }); + + it('handles nested parentheses in aliases', () => { + const config = 'one (1), two (2),three (3 (three))'; + + expect(transformFields(config)).toEqual([ + { + name: 'one', + alias: '1', + }, + { + name: 'two', + alias: '2', + }, + { + name: 'three', + alias: '3 (three)', + }, + ]); + }); +}); diff --git a/functions/configs.js b/functions/configs.js index 564b3ce5..3ae8514f 100644 --- a/functions/configs.js +++ b/functions/configs.js @@ -278,7 +278,9 @@ async function validateRelationshipClasses( /** * @param {FieldValidation} fieldValidation * @param {string[]} serviceFieldNames - * @param {Object} config + * @param {import('./common/config.js').QueryLayerConfig & + * import('./common/config.js').RelatedTableConfig & + * import('./common/config.js').RelationshipClassConfig} config * @param {string} configName * @returns {string[] | boolean} */ @@ -299,7 +301,11 @@ export function validateFields( validationFieldNames = [configValue]; } else { // must be array - validationFieldNames = configValue?.length ? configValue : []; + validationFieldNames = configValue?.length + ? configValue.map((value) => + typeof value === 'string' ? value : value.name, + ) + : []; } } else { configProp = fieldValidation.configProp; diff --git a/functions/configs.test.js b/functions/configs.test.js index 2d218e8a..96fcb4d7 100644 --- a/functions/configs.test.js +++ b/functions/configs.test.js @@ -118,6 +118,7 @@ describe('validateFields', () => { const serviceFieldNames = ['one']; expect( + // @ts-ignore validateFields(fieldValidation, serviceFieldNames, config, tableName), ).toMatchInlineSnapshot(` [ @@ -131,6 +132,7 @@ describe('validateFields', () => { const serviceFieldNames = ['one']; expect( + // @ts-ignore validateFields(fieldValidation, serviceFieldNames, config, tableName), ).toBe(true); }); @@ -142,6 +144,7 @@ describe('validateFields', () => { const serviceFieldNames = ['one', 'two', 'three']; expect( + // @ts-ignore validateFields(fieldValidation, serviceFieldNames, config, tableName), ).toBe(true); }); @@ -150,6 +153,7 @@ describe('validateFields', () => { const serviceFieldNames = ['one']; expect( + // @ts-ignore validateFields(fieldValidation, serviceFieldNames, config, tableName), ).toBe(true); @@ -159,6 +163,7 @@ describe('validateFields', () => { }; expect( + // @ts-ignore validateFields(fieldValidation2, serviceFieldNames, config, tableName), ).toBe(true); }); diff --git a/src/components/Map.jsx b/src/components/Map.jsx index 458e146e..70c04a46 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -379,7 +379,9 @@ export default function MapComponent() { const query = featureLayer.createQuery(); query.where = featureLayer.definitionExpression; query.outFields = [ - ...layer[fieldNames.queryLayers.resultGridFields], + ...layer[fieldNames.queryLayers.resultGridFields].map((value) => + typeof value === 'string' ? value : value.name, + ), featureServiceJson.objectIdField || 'OBJECTID', ]; query.returnGeometry = false; diff --git a/src/components/RelatedRecords.jsx b/src/components/RelatedRecords.jsx index 1dead983..692f0c05 100644 --- a/src/components/RelatedRecords.jsx +++ b/src/components/RelatedRecords.jsx @@ -4,7 +4,7 @@ import * as Tabs from '../utah-design-system/Tabs'; import { useQuery } from '@tanstack/react-query'; import Spinner from '../utah-design-system/Spinner'; import ky from 'ky'; -import { getConfigByTableName } from '../utils'; +import { getAlias, getConfigByTableName } from '../utils'; import SimpleTable from '../utah-design-system/SimpleTable'; import { useCallback, useEffect, useState } from 'react'; import Tag from './Tag'; @@ -116,18 +116,16 @@ function TabContent({ searchParams: params, }).json(); - const oidField = featureServiceJson.objectIdField || 'OBJECTID'; - return { childTableName, tabName: childConfig[fieldNames.relatedTables.tabName], rows: relatedRecords.features.map((feature) => feature.attributes), - columns: featureServiceJson.fields - .filter((field) => field.name !== oidField) - .map((field) => ({ - Header: field.alias, - accessorKey: field.name, - })), + columns: childConfig[fieldNames.relatedTables.gridFields].map( + (value) => ({ + header: value.alias || getAlias(value, featureServiceJson.fields), + accessorKey: value.name || value, + }), + ), }; } diff --git a/src/components/ResultTable.jsx b/src/components/ResultTable.jsx index 2f075dfa..356a32a3 100644 --- a/src/components/ResultTable.jsx +++ b/src/components/ResultTable.jsx @@ -100,10 +100,11 @@ export default function ResultTable({ const columns = useMemo( () => + // field could be a string or object queryLayerResult[fieldNames.queryLayers.resultGridFields].map( - (field) => ({ - accessorKey: field, - header: getAlias(field, queryLayerResult.fields), + (value) => ({ + accessorKey: value.name || value, + header: value.alias || getAlias(value, queryLayerResult.fields), }), ), [queryLayerResult], diff --git a/src/components/search-wizard/QueryLayer.stories.jsx b/src/components/search-wizard/QueryLayer.stories.jsx index ecf18afb..75e3f94d 100644 --- a/src/components/search-wizard/QueryLayer.stories.jsx +++ b/src/components/search-wizard/QueryLayer.stories.jsx @@ -32,6 +32,7 @@ const config = { 'Identify Attributes': 'SYSFACID (System-Facility ID), SYSNUMBER (System Number), SYSNAME (System Name), FACID (Facility Identifier), FACNAME (Facility Name), FACTYPEDESC (Facility Type Description), FACTYPECODE (Facility Type Code), FACACTIVITY (Facility Activity Status), SYSTYPE (System Type), SYSACTIVITY (System Activity Status), SYSPOPULATION (System Population), SYSPOPWHSALE (System Wholesale Population), SYSPHONE (System Phone), SYSPHONEEXT (System Phone Extension), SYSADDRESS1 (System Address1), SYSADDRESS2 (System Address2), SYSCITY (System City), SYSSTATE (System State), SYSZIP (System ZIP Code)\\n', 'Related Tables': 'None', + 'Result Grid Fields': [], 'Document Search': 'http://168.178.6.56/TabsPage.aspx?AI_PageConfigID=100001&DivName=DDW', 'GRAMA Request': diff --git a/src/components/search-wizard/filters/utils.test.js b/src/components/search-wizard/filters/utils.test.js index ca74d4f1..89228489 100644 --- a/src/components/search-wizard/filters/utils.test.js +++ b/src/components/search-wizard/filters/utils.test.js @@ -52,6 +52,7 @@ describe('getWhere', () => { 'Sort Field': '', 'Identify Attributes': '', 'Related Tables': '', + 'Result Grid Fields': [], 'Document Search': '', 'GRAMA Request': '', 'Permit Information': '', diff --git a/src/utah-design-system/SimpleTable.stories.jsx b/src/utah-design-system/SimpleTable.stories.jsx index 0ec3a359..868e8e8f 100644 --- a/src/utah-design-system/SimpleTable.stories.jsx +++ b/src/utah-design-system/SimpleTable.stories.jsx @@ -184,91 +184,69 @@ const data = [ ]; const columns = [ { - Header: 'FACILITYID', accessorKey: 'FACILITYID', }, { - Header: 'TANKID', accessorKey: 'TANKID', }, { - Header: 'ALTTANKID', accessorKey: 'ALTTANKID', }, { - Header: 'FEDERALREG', accessorKey: 'FEDERALREG', }, { - Header: 'ABOVETANK', accessorKey: 'ABOVETANK', }, { - Header: 'REGAST', accessorKey: 'REGAST', }, { - Header: 'TANKEMERGE', accessorKey: 'TANKEMERGE', }, { - Header: 'TANKSTATUS', accessorKey: 'TANKSTATUS', }, { - Header: 'TANKCAPACI', accessorKey: 'TANKCAPACI', }, { - Header: 'SUBSTANCED', accessorKey: 'SUBSTANCED', }, { - Header: 'SUBSTANCET', accessorKey: 'SUBSTANCET', }, { - Header: 'TANKMATDES', accessorKey: 'TANKMATDES', }, { - Header: 'TANKMODSDE', accessorKey: 'TANKMODSDE', }, { - Header: 'PIPEMATDES', accessorKey: 'PIPEMATDES', }, { - Header: 'PIPEMODDES', accessorKey: 'PIPEMODDES', }, { - Header: 'DATEINSTAL', accessorKey: 'DATEINSTAL', }, { - Header: 'DATECLOSE', accessorKey: 'DATECLOSE', }, { - Header: 'CLOSURESTA', accessorKey: 'CLOSURESTA', }, { - Header: 'INCOMPLIAN', accessorKey: 'INCOMPLIAN', }, { - Header: 'PST_FUND', accessorKey: 'PST_FUND', }, { - Header: 'OTHERTYPE', accessorKey: 'OTHERTYPE', }, { - Header: 'FORKLIFT_HASH', accessorKey: 'FORKLIFT_HASH', }, ];