From ef76f323672e32d3d48040a5b7f0c00d12c96c5d Mon Sep 17 00:00:00 2001 From: Kirk Swenson Date: Wed, 4 Dec 2024 16:20:01 -0800 Subject: [PATCH 1/2] wip: update import attribute types --- .../axis-or-legend-attribute-menu.tsx | 2 +- .../edit-attribute-properties-modal.tsx | 2 +- .../components/attribute-label.tsx | 2 +- .../legend/legend-attribute-label.tsx | 2 +- .../models/data-configuration-model.ts | 2 +- .../components/graph-attribute-label.tsx | 2 +- .../graph/components/graph-axis.tsx | 2 +- v3/src/components/graph/components/graph.tsx | 2 +- .../models/graph-data-configuration-model.ts | 2 +- .../data-interactive-types.ts | 9 +++--- .../handlers/di-handler-utils.ts | 3 +- v3/src/hooks/use-drop-hint-string.test.ts | 4 +-- v3/src/hooks/use-drop-hint-string.ts | 2 +- v3/src/models/data/attribute-types.ts | 28 ++++++++++++++++++- v3/src/models/data/attribute.test.ts | 5 ++-- v3/src/models/data/attribute.ts | 26 ++--------------- v3/src/models/data/data-set.ts | 3 +- v3/src/models/formula/formula-types.ts | 2 +- v3/src/v2/codap-v2-document.ts | 2 +- v3/src/v2/codap-v2-types.ts | 2 +- 20 files changed, 56 insertions(+), 48 deletions(-) diff --git a/v3/src/components/axis/components/axis-or-legend-attribute-menu.tsx b/v3/src/components/axis/components/axis-or-legend-attribute-menu.tsx index 374d570d7a..9a46ec69e4 100644 --- a/v3/src/components/axis/components/axis-or-legend-attribute-menu.tsx +++ b/v3/src/components/axis/components/axis-or-legend-attribute-menu.tsx @@ -6,7 +6,7 @@ import { graphPlaceToAttrRole } from "../../data-display/data-display-types" import { useDataConfigurationContext } from "../../data-display/hooks/use-data-configuration-context" import { useOutsidePointerDown } from "../../../hooks/use-outside-pointer-down" import { useOverlayBounds } from "../../../hooks/use-overlay-bounds" -import { AttributeType } from "../../../models/data/attribute" +import { AttributeType } from "../../../models/data/attribute-types" import { IDataSet } from "../../../models/data/data-set" import {IUseDraggableAttribute, useDraggableAttribute} from "../../../hooks/use-drag-drop" import {useInstanceIdContext} from "../../../hooks/use-instance-id-context" diff --git a/v3/src/components/case-tile-common/attribute-menu/edit-attribute-properties-modal.tsx b/v3/src/components/case-tile-common/attribute-menu/edit-attribute-properties-modal.tsx index 4cd5a7b8fe..f155f2bf64 100644 --- a/v3/src/components/case-tile-common/attribute-menu/edit-attribute-properties-modal.tsx +++ b/v3/src/components/case-tile-common/attribute-menu/edit-attribute-properties-modal.tsx @@ -3,7 +3,7 @@ import { Button, FormControl, FormLabel, HStack, Input, ModalBody, ModalCloseBut import React, { useEffect, useState } from "react" import { useDataSetContext } from "../../../hooks/use-data-set-context" import { logMessageWithReplacement } from "../../../lib/log-message" -import { AttributeType, attributeTypes } from "../../../models/data/attribute" +import { AttributeType, attributeTypes } from "../../../models/data/attribute-types" import { updateAttributesNotification } from "../../../models/data/data-set-notifications" import { DatePrecision } from "../../../utilities/date-utils" import { uniqueName } from "../../../utilities/js-utils" diff --git a/v3/src/components/data-display/components/attribute-label.tsx b/v3/src/components/data-display/components/attribute-label.tsx index 9ec44b0386..7557f50375 100644 --- a/v3/src/components/data-display/components/attribute-label.tsx +++ b/v3/src/components/data-display/components/attribute-label.tsx @@ -6,7 +6,7 @@ import { GraphPlace } from "../../axis-graph-shared" import { useDataDisplayLayout } from "../hooks/use-data-display-layout" import { useDataDisplayModelContext } from "../hooks/use-data-display-model" import { AxisOrLegendAttributeMenu } from "../../axis/components/axis-or-legend-attribute-menu" -import { AttributeType } from "../../../models/data/attribute" +import { AttributeType } from "../../../models/data/attribute-types" import { mstAutorun } from "../../../utilities/mst-autorun" import "./attribute-label.scss" diff --git a/v3/src/components/data-display/components/legend/legend-attribute-label.tsx b/v3/src/components/data-display/components/legend/legend-attribute-label.tsx index 59a0d4a797..a32f5b4f25 100644 --- a/v3/src/components/data-display/components/legend/legend-attribute-label.tsx +++ b/v3/src/components/data-display/components/legend/legend-attribute-label.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useEffect, useRef} from "react" import {select} from "d3" -import {AttributeType} from "../../../../models/data/attribute" +import {AttributeType} from "../../../../models/data/attribute-types" import {IDataSet} from "../../../../models/data/data-set" import {axisGap} from "../../../axis/axis-types" import {GraphPlace} from "../../../axis-graph-shared" diff --git a/v3/src/components/data-display/models/data-configuration-model.ts b/v3/src/components/data-display/models/data-configuration-model.ts index e680d0bb84..936acd185d 100644 --- a/v3/src/components/data-display/models/data-configuration-model.ts +++ b/v3/src/components/data-display/models/data-configuration-model.ts @@ -8,7 +8,7 @@ import {applyModelChange} from "../../../models/history/apply-model-change" import {cachedFnWithArgsFactory} from "../../../utilities/mst-utils" import { isFiniteNumber } from "../../../utilities/math-utils" import { stringValuesToDateSeconds } from "../../../utilities/date-utils" -import {AttributeType, attributeTypes} from "../../../models/data/attribute" +import {AttributeType, attributeTypes} from "../../../models/data/attribute-types" import {DataSet, IDataSet} from "../../../models/data/data-set" import {ICase} from "../../../models/data/data-set-types" import {idOfChildmostCollectionForAttributes} from "../../../models/data/data-set-utils" diff --git a/v3/src/components/graph/components/graph-attribute-label.tsx b/v3/src/components/graph/components/graph-attribute-label.tsx index b067aa9fff..d53e1f4bf9 100644 --- a/v3/src/components/graph/components/graph-attribute-label.tsx +++ b/v3/src/components/graph/components/graph-attribute-label.tsx @@ -4,7 +4,7 @@ import { t } from "../../../utilities/translation/translate" import { useGraphDataConfigurationContext } from "../hooks/use-graph-data-configuration-context" import { useGraphContentModelContext } from "../hooks/use-graph-content-model-context" import { useGraphLayoutContext } from "../hooks/use-graph-layout-context" -import { AttributeType } from "../../../models/data/attribute" +import { AttributeType } from "../../../models/data/attribute-types" import { IDataSet } from "../../../models/data/data-set" import { GraphPlace, isVertical } from "../../axis-graph-shared" import { AttributeLabel } from "../../data-display/components/attribute-label" diff --git a/v3/src/components/graph/components/graph-axis.tsx b/v3/src/components/graph/components/graph-axis.tsx index fd57d26153..935163c6de 100644 --- a/v3/src/components/graph/components/graph-axis.tsx +++ b/v3/src/components/graph/components/graph-axis.tsx @@ -10,7 +10,7 @@ import {useInstanceIdContext} from "../../../hooks/use-instance-id-context" import {useGraphDataConfigurationContext} from "../hooks/use-graph-data-configuration-context" import {useGraphContentModelContext} from "../hooks/use-graph-content-model-context" import {useGraphLayoutContext} from "../hooks/use-graph-layout-context" -import {AttributeType} from "../../../models/data/attribute" +import {AttributeType} from "../../../models/data/attribute-types" import {IDataSet} from "../../../models/data/data-set" import {AxisPlace} from "../../axis/axis-types" import {Axis} from "../../axis/components/axis" diff --git a/v3/src/components/graph/components/graph.tsx b/v3/src/components/graph/components/graph.tsx index b82d17172d..dc5f58f671 100644 --- a/v3/src/components/graph/components/graph.tsx +++ b/v3/src/components/graph/components/graph.tsx @@ -33,7 +33,7 @@ import {GraphPlace} from "../../axis-graph-shared" import {MarqueeState} from "../../data-display/models/marquee-state" import {DataTip} from "../../data-display/components/data-tip" import {MultiLegend} from "../../data-display/components/legend/multi-legend" -import {AttributeType} from "../../../models/data/attribute" +import {AttributeType} from "../../../models/data/attribute-types" import {IDataSet} from "../../../models/data/data-set" import {isUndoingOrRedoing} from "../../../models/history/tree-types" import {useDataDisplayAnimation} from "../../data-display/hooks/use-data-display-animation" diff --git a/v3/src/components/graph/models/graph-data-configuration-model.ts b/v3/src/components/graph/models/graph-data-configuration-model.ts index 32888d135a..4e8d87bebc 100644 --- a/v3/src/components/graph/models/graph-data-configuration-model.ts +++ b/v3/src/components/graph/models/graph-data-configuration-model.ts @@ -1,6 +1,6 @@ import {addDisposer, getSnapshot, Instance, SnapshotIn, types} from "mobx-state-tree" import {comparer, reaction} from "mobx" -import {AttributeType} from "../../../models/data/attribute" +import {AttributeType} from "../../../models/data/attribute-types" import {IDataSet} from "../../../models/data/data-set" import {typedId} from "../../../utilities/js-utils" import { cachedFnFactory, cachedFnWithArgsFactory, safeGetSnapshot } from "../../../utilities/mst-utils" diff --git a/v3/src/data-interactive/data-interactive-types.ts b/v3/src/data-interactive/data-interactive-types.ts index b8edf0207f..65ac5c4f63 100644 --- a/v3/src/data-interactive/data-interactive-types.ts +++ b/v3/src/data-interactive/data-interactive-types.ts @@ -1,15 +1,16 @@ import { RequireAtLeastOne } from "type-fest" +import { LoggableValue } from "../lib/log-message" import { IAttribute } from "../models/data/attribute" -import { ICodapV2Attribute, ICodapV2Collection, ICodapV2DataContext } from "../v2/codap-v2-types" +import { IValueType } from "../models/data/attribute-types" +import { ICollectionLabels, ICollectionModel } from "../models/data/collection" import { IDataSet } from "../models/data/data-set" import { ICase, ICaseID } from "../models/data/data-set-types" import { IGlobalValue } from "../models/global/global-value" import { ITileModel } from "../models/tiles/tile-model" -import { ICollectionLabels, ICollectionModel } from "../models/data/collection" +import { ICodapV2Attribute, ICodapV2Collection, ICodapV2DataContext } from "../v2/codap-v2-types" import { V2SpecificComponent } from "./data-interactive-component-types" -import { LoggableValue } from "../lib/log-message" -export type DICaseValue = string | number | boolean | Date | undefined +export type DICaseValue = IValueType export type DICaseValues = Record export interface DIFullCase { children?: number[] diff --git a/v3/src/data-interactive/handlers/di-handler-utils.ts b/v3/src/data-interactive/handlers/di-handler-utils.ts index c40e146239..164ac48e76 100644 --- a/v3/src/data-interactive/handlers/di-handler-utils.ts +++ b/v3/src/data-interactive/handlers/di-handler-utils.ts @@ -1,4 +1,5 @@ -import { IAttribute, isAttributeType } from "../../models/data/attribute" +import { IAttribute } from "../../models/data/attribute" +import { isAttributeType } from "../../models/data/attribute-types" import { ICollectionModel } from "../../models/data/collection" import { IDataSet } from "../../models/data/data-set" import { IAddCollectionOptions } from "../../models/data/data-set-types" diff --git a/v3/src/hooks/use-drop-hint-string.test.ts b/v3/src/hooks/use-drop-hint-string.test.ts index e28aabe84b..12bee9e8d8 100644 --- a/v3/src/hooks/use-drop-hint-string.test.ts +++ b/v3/src/hooks/use-drop-hint-string.test.ts @@ -1,6 +1,6 @@ -import { AttributeType } from "../models/data/attribute" -import {determineBaseString} from "./use-drop-hint-string" import {GraphAttrRole} from "../components/data-display/data-display-types" +import {AttributeType} from "../models/data/attribute-types" +import {determineBaseString} from "./use-drop-hint-string" interface Scenario { role: GraphAttrRole diff --git a/v3/src/hooks/use-drop-hint-string.ts b/v3/src/hooks/use-drop-hint-string.ts index 5a03ca181d..7beb70671a 100644 --- a/v3/src/hooks/use-drop-hint-string.ts +++ b/v3/src/hooks/use-drop-hint-string.ts @@ -1,5 +1,5 @@ import {useDndContext} from "@dnd-kit/core" -import {AttributeType} from "../models/data/attribute" +import {AttributeType} from "../models/data/attribute-types" import {getDragAttributeInfo} from "./use-drag-drop" import {GraphPlace} from "../components/axis-graph-shared" import {GraphAttrRole, attrRoleToGraphPlace} from "../components/data-display/data-display-types" diff --git a/v3/src/models/data/attribute-types.ts b/v3/src/models/data/attribute-types.ts index 5408b7eabf..5fe2235f83 100644 --- a/v3/src/models/data/attribute-types.ts +++ b/v3/src/models/data/attribute-types.ts @@ -1,7 +1,33 @@ +import { formatStdISODateString } from "../../utilities/date-iso-utils" + export const kDefaultNumPrecision = 3 export const kDefaultNumFormatStr = `.${kDefaultNumPrecision}~f` export const isDevelopment = () => process.env.NODE_ENV !== "production" export const isProduction = () => process.env.NODE_ENV === "production" -export type IValueType = string | number | boolean | Date | undefined +export type IValueType = string | number | boolean | Date | object | undefined + +export function importValueToString(value: IValueType): string { + if (value == null) { + return "" + } + if (typeof value === "string") { + return value + } + if (value instanceof Date) { + return formatStdISODateString(value) + } + if (typeof value === "object") { + return JSON.stringify(value) + } + return value.toString() +} + +export const attributeTypes = [ + "categorical", "numeric", "date", "qualitative", "boundary", "checkbox", "color" +] as const +export type AttributeType = typeof attributeTypes[number] +export function isAttributeType(type?: string | null): type is AttributeType { + return type != null && (attributeTypes as readonly string[]).includes(type) +} diff --git a/v3/src/models/data/attribute.test.ts b/v3/src/models/data/attribute.test.ts index 98dbe4b784..9f0a03fb82 100644 --- a/v3/src/models/data/attribute.test.ts +++ b/v3/src/models/data/attribute.test.ts @@ -1,9 +1,8 @@ import { cloneDeep } from "lodash" import { reaction } from "mobx" import { getSnapshot } from "mobx-state-tree" -import { - Attribute, IAttributeSnapshot, importValueToString, isAttributeType, isFormulaAttr, isValidFormulaAttr -} from "./attribute" +import { Attribute, IAttributeSnapshot, isFormulaAttr, isValidFormulaAttr } from "./attribute" +import { isAttributeType, importValueToString } from "./attribute-types" describe("Attribute", () => { diff --git a/v3/src/models/data/attribute.ts b/v3/src/models/data/attribute.ts index 3166916649..bdfb52d031 100644 --- a/v3/src/models/data/attribute.ts +++ b/v3/src/models/data/attribute.ts @@ -28,7 +28,6 @@ import { Instance, SnapshotIn, types } from "mobx-state-tree" import { kAttrIdPrefix, typeV3Id } from "../../utilities/codap-utils" import { parseColor } from "../../utilities/color-utils" -import { formatStdISODateString } from "../../utilities/date-iso-utils" import { isDateString } from "../../utilities/date-parser" import { DatePrecision } from "../../utilities/date-utils" import { cachedFnFactory } from "../../utilities/mst-utils" @@ -36,34 +35,15 @@ import { isBoundaryValue, kPolygonNames } from "../boundaries/boundary-types" import { Formula, IFormula } from "../formula/formula" import { applyModelChange } from "../history/apply-model-change" import { withoutUndo } from "../history/without-undo" -import { isDevelopment, isProduction, IValueType } from "./attribute-types" +import { + AttributeType, attributeTypes, importValueToString, isDevelopment, isProduction, IValueType +} from "./attribute-types" import { V2Model } from "./v2-model" export interface ISetValueOptions { noInvalidate?: boolean } -export function importValueToString(value: IValueType): string { - if (value == null) { - return "" - } - if (typeof value === "string") { - return value - } - if (value instanceof Date) { - return formatStdISODateString(value) - } - return value.toString() -} - -export const attributeTypes = [ - "categorical", "numeric", "date", "qualitative", "boundary", "checkbox", "color" -] as const -export type AttributeType = typeof attributeTypes[number] -export function isAttributeType(type?: string | null): type is AttributeType { - return type != null && (attributeTypes as readonly string[]).includes(type) -} - export const Attribute = V2Model.named("Attribute").props({ id: typeV3Id(kAttrIdPrefix), _cid: types.maybe(types.string), // cid was a v2 property that is used by some plugins (Collaborative) diff --git a/v3/src/models/data/data-set.ts b/v3/src/models/data/data-set.ts index 778c3b70e3..24a9dcafa5 100644 --- a/v3/src/models/data/data-set.ts +++ b/v3/src/models/data/data-set.ts @@ -48,6 +48,7 @@ import { } from "mobx-state-tree" import pluralize from "pluralize" import { Attribute, IAttribute, IAttributeSnapshot } from "./attribute" +import { importValueToString } from "./attribute-types" import { CollectionModel, ICollectionModel, ICollectionModelSnapshot, IItemData, isCollectionModel, syncCollectionLinks } from "./collection" @@ -110,7 +111,7 @@ export function toCanonicalCase(ds: IDataSet, aCase: ICase | ICaseCreation): ICa if (key !== "__id__") { const id = ds.attrIDFromName(key) if (id) { - canonical[id] = aCase[key] + canonical[id] = importValueToString(aCase[key] ?? "") } else { console.warn(`Dataset.toCanonical failed to convert attribute: "${key}"`) diff --git a/v3/src/models/formula/formula-types.ts b/v3/src/models/formula/formula-types.ts index 05753089a6..7a5b232ecc 100644 --- a/v3/src/models/formula/formula-types.ts +++ b/v3/src/models/formula/formula-types.ts @@ -41,7 +41,7 @@ export interface ILookupDependency { export type IFormulaDependency = ILocalAttributeDependency | IBoundaryDependency | IGlobalValueDependency | ILookupDependency -export type FValue = string | number | boolean | Date +export type FValue = string | number | boolean | Date | object export type FValueOrArray = FValue | FValue[] diff --git a/v3/src/v2/codap-v2-document.ts b/v3/src/v2/codap-v2-document.ts index d59bd07755..c66536a5fa 100644 --- a/v3/src/v2/codap-v2-document.ts +++ b/v3/src/v2/codap-v2-document.ts @@ -206,7 +206,7 @@ export class CodapV2Document { if (level === 0) { // FIXME: values can include objects not just the primitives defined by IValueType // FIXME: should the itemID overwrite any __id__ returned by toCanonical? - let itemValues = { ...toCanonical(data, values as any), __id__: itemID } + let itemValues = { __id__: itemID, ...toCanonical(data, values) } // look up parent case attributes and add them to caseValues for (let parentCase = this.getParentCase(_case); parentCase; parentCase = this.getParentCase(parentCase)) { itemValues = { diff --git a/v3/src/v2/codap-v2-types.ts b/v3/src/v2/codap-v2-types.ts index 3da120b88e..f0f22a88d6 100644 --- a/v3/src/v2/codap-v2-types.ts +++ b/v3/src/v2/codap-v2-types.ts @@ -1,5 +1,5 @@ import { SetOptional } from "type-fest" -import {AttributeType} from "../models/data/attribute" +import { AttributeType } from "../models/data/attribute-types" export interface ICodapV2Attribute { guid: number From b51a36a0f1a4bdc87386815c43b88ef575d173f9 Mon Sep 17 00:00:00 2001 From: Kirk Swenson Date: Wed, 11 Dec 2024 09:39:13 -0800 Subject: [PATCH 2/2] chore: code review tweaks --- v3/src/models/data/attribute.test.ts | 1 + v3/src/v2/codap-v2-document.ts | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/v3/src/models/data/attribute.test.ts b/v3/src/models/data/attribute.test.ts index 9f0a03fb82..94d1d26218 100644 --- a/v3/src/models/data/attribute.test.ts +++ b/v3/src/models/data/attribute.test.ts @@ -46,6 +46,7 @@ describe("Attribute", () => { expect(importValueToString(1e-6)).toBe("0.000001") expect(importValueToString(true)).toBe("true") expect(importValueToString(false)).toBe("false") + expect(importValueToString({ foo: "bar" })).toBe(`{"foo":"bar"}`) expect(importValueToString(new Date(2020, 5, 14, 10, 13, 34, 123))).toBe("2020-06-14T10:13:34.123Z") const attr = Attribute.create({ name: "a" }) diff --git a/v3/src/v2/codap-v2-document.ts b/v3/src/v2/codap-v2-document.ts index c66536a5fa..5181e5b220 100644 --- a/v3/src/v2/codap-v2-document.ts +++ b/v3/src/v2/codap-v2-document.ts @@ -204,14 +204,11 @@ export class CodapV2Document { this.guidMap.set(guid, { type: "DG.Case", object: _case }) // for level 0 (child-most collection), add items with their item ids and stash case ids if (level === 0) { - // FIXME: values can include objects not just the primitives defined by IValueType - // FIXME: should the itemID overwrite any __id__ returned by toCanonical? let itemValues = { __id__: itemID, ...toCanonical(data, values) } // look up parent case attributes and add them to caseValues for (let parentCase = this.getParentCase(_case); parentCase; parentCase = this.getParentCase(parentCase)) { itemValues = { - // FIXME: see above - ...(parentCase.values ? toCanonical(data, parentCase.values as any) : undefined), + ...(parentCase.values ? toCanonical(data, parentCase.values) : undefined), ...itemValues } }