From 52c415bd9f0fe287e2de7e56340ea2527c5d837d Mon Sep 17 00:00:00 2001 From: Teale Fristoe Date: Tue, 28 May 2024 14:04:34 -0700 Subject: [PATCH] 187690238 v3 DI Collection Requests (#1280) * Handle update collection requests. * Handle delete collection requests. --- .../data-interactive-type-utils.ts | 10 ++-- .../data-interactive-types.ts | 11 ++++- .../handlers/collection-handler.test.ts | 31 +++++++++++- .../handlers/collection-handler.ts | 35 ++++++++++++-- v3/src/models/data/collection.test.ts | 40 ++++++++++++++++ v3/src/models/data/collection.ts | 47 +++++++++++++++++++ v3/src/models/data/data-set.ts | 9 ++++ 7 files changed, 174 insertions(+), 9 deletions(-) diff --git a/v3/src/data-interactive/data-interactive-type-utils.ts b/v3/src/data-interactive/data-interactive-type-utils.ts index c26bc8a794..7ce6b612ba 100644 --- a/v3/src/data-interactive/data-interactive-type-utils.ts +++ b/v3/src/data-interactive/data-interactive-type-utils.ts @@ -1,3 +1,4 @@ +import { getSnapshot } from "mobx-state-tree" import { IAttribute, IAttributeSnapshot } from "../models/data/attribute" import { ICollectionModel, ICollectionPropsModel } from "../models/data/collection" import { IDataSet } from "../models/data/data-set" @@ -138,8 +139,9 @@ export function convertAttributeToV2FromResources(resources: DIResources) { } export function convertCollectionToV2(collection: ICollectionModel, dataContext?: IDataSet): ICodapV2CollectionV3 { - const { name, title, id } = collection + const { name, title, id, labels: _labels } = collection const v2Id = toV2Id(id) + const labels = _labels ? getSnapshot(_labels) : undefined const v2Attrs = collection.attributes.map(attribute => { if (attribute) return convertAttributeToV2(attribute, dataContext) }) @@ -154,7 +156,7 @@ export function convertCollectionToV2(collection: ICollectionModel, dataContext? // collapseChildren, guid: v2Id, id: v2Id, - // labels: { singleCase, pluralCase } + labels, name, // parent, title, @@ -165,13 +167,15 @@ export function convertCollectionToV2(collection: ICollectionModel, dataContext? export function convertUngroupedCollectionToV2(dataContext: IDataSet): ICodapV2CollectionV3 | undefined { // TODO This will probably need to be reworked after upcoming v3 collection overhaul, // so I'm leaving it bare bones for now. - const { name, title, id } = dataContext.ungrouped + const { name, title, id, labels: _labels } = dataContext.ungrouped const v2Id = toV2Id(id) + const labels = _labels ? getSnapshot(_labels) : undefined const ungroupedAttributes = dataContext.ungroupedAttributes if (ungroupedAttributes.length > 0) { return { guid: v2Id, id: v2Id, + labels, name, title, attrs: ungroupedAttributes.map(attr => convertAttributeToV2(attr, dataContext)), diff --git a/v3/src/data-interactive/data-interactive-types.ts b/v3/src/data-interactive/data-interactive-types.ts index 66224307c1..6f2859a229 100644 --- a/v3/src/data-interactive/data-interactive-types.ts +++ b/v3/src/data-interactive/data-interactive-types.ts @@ -5,7 +5,7 @@ import { IDataSet } from "../models/data/data-set" import { ICase } from "../models/data/data-set-types" import { IGlobalValue } from "../models/global/global-value" import { ITileModel } from "../models/tiles/tile-model" -import { ICollectionPropsModel } from "../models/data/collection" +import { ICollectionLabels, ICollectionPropsModel } from "../models/data/collection" import { V2Component } from "./data-interactive-component-types" export type DICaseValue = string | number | boolean | undefined @@ -107,6 +107,13 @@ export interface DINewCase { export interface DIUpdateCase { values: DICaseValues } +export interface DIDeleteCollectionResult { + collections?: number[] +} +export interface DIUpdateCollection { + title?: string + labels?: Partial +} export interface DINotification { request?: string } @@ -143,7 +150,7 @@ export type DIValues = DISingleValues | DISingleValues[] | number | string[] export type DIResultAttributes = { attrs: ICodapV2AttributeV3[] } export type DIResultSingleValues = DICase | DIComponentInfo | DIGetCaseResult | DIGlobal | DIInteractiveFrame export type DIResultValues = DIResultSingleValues | DIResultSingleValues[] | - DIAllCases | DIResultAttributes | number + DIAllCases | DIDeleteCollectionResult | DIResultAttributes | number export interface DIMetadata { dirtyDocument?: boolean diff --git a/v3/src/data-interactive/handlers/collection-handler.test.ts b/v3/src/data-interactive/handlers/collection-handler.test.ts index 7d0cae9df1..25b281aacb 100644 --- a/v3/src/data-interactive/handlers/collection-handler.test.ts +++ b/v3/src/data-interactive/handlers/collection-handler.test.ts @@ -1,6 +1,6 @@ import { ICodapV2CollectionV3 } from "../../v2/codap-v2-types" import { toV2Id, toV3CollectionId } from "../../utilities/codap-utils" -import { DICollection } from "../data-interactive-types" +import { DICollection, DIDeleteCollectionResult, DIValues } from "../data-interactive-types" import { diCollectionHandler } from "./collection-handler" import { setupTestDataset } from "./handler-test-utils" @@ -62,18 +62,34 @@ describe("DataInteractive CollectionHandler", () => { expect(dataset.collections[4].attributes[1]?.name).toBe("a7") }) + it("delete works", () => { + const { dataset: dataContext, c1: collection } = setupTestDataset() + expect(handler.delete?.({ dataContext }).success).toBe(false) + expect(handler.delete?.({ collection }).success).toBe(false) + + const collectionId = collection.id + const result = handler.delete?.({ dataContext, collection }) + expect(result?.success).toBe(true) + expect((result?.values as DIDeleteCollectionResult).collections?.[0]).toBe(toV2Id(collectionId)) + expect(dataContext.attributes.length).toBe(2) + expect(dataContext.collections.length).toBe(1) + expect(dataContext.getCollection(collectionId)).toBeUndefined() + }) + it("get works", () => { const { dataset, c1 } = setupTestDataset() expect(handler.get?.({}).success).toBe(false) expect(handler.get?.({ dataContext: dataset }).success).toBe(false) // Grouped collection + c1.setLabels({ singleCase: "singleCase" }) const groupedResult = handler.get?.({ dataContext: dataset, collection: c1 }) expect(groupedResult?.success).toBe(true) const groupedValues = groupedResult?.values as ICodapV2CollectionV3 expect(groupedValues.name).toEqual(c1.name) expect(groupedValues.id).toEqual(toV2Id(c1.id)) expect(groupedValues.attrs.length).toEqual(c1.attributes.length) + expect(groupedValues.labels?.singleCase).toBe("singleCase") // Ungrouped collection const ungrouped = dataset.ungrouped @@ -84,4 +100,17 @@ describe("DataInteractive CollectionHandler", () => { expect(ungroupedValues.id).toEqual(toV2Id(ungrouped.id)) expect(ungroupedValues.attrs.length).toEqual(dataset.ungroupedAttributes.length) }) + + it("update works", () => { + const { dataset: dataContext, c1: collection } = setupTestDataset() + expect(handler.update?.({ dataContext }).success).toBe(false) + expect(handler.update?.({ collection }).success).toBe(false) + expect(handler.update?.({ dataContext, collection }).success).toBe(true) + + expect(handler.update?.( + { dataContext, collection }, { title: "newTitle", labels: { singleCase: "singleCase" } } as DIValues + ).success).toBe(true) + expect(collection._title).toBe("newTitle") + expect(collection.labels?.singleCase).toBe("singleCase") + }) }) diff --git a/v3/src/data-interactive/handlers/collection-handler.ts b/v3/src/data-interactive/handlers/collection-handler.ts index a9148c2120..467605c399 100644 --- a/v3/src/data-interactive/handlers/collection-handler.ts +++ b/v3/src/data-interactive/handlers/collection-handler.ts @@ -4,7 +4,7 @@ import { getSharedCaseMetadataFromDataset } from "../../models/shared/shared-dat import { toV2Id } from "../../utilities/codap-utils" import { registerDIHandler } from "../data-interactive-handler" import { - DIHandler, DIResources, diNotImplementedYet, DIValues, DICreateCollection, DICollection + DIHandler, DIResources, DIValues, DICreateCollection, DICollection, DIUpdateCollection } from "../data-interactive-types" import { convertCollectionToV2, convertUngroupedCollectionToV2 } from "../data-interactive-type-utils" import { getCollection } from "../data-interactive-utils" @@ -78,7 +78,21 @@ export const diCollectionHandler: DIHandler = { return { success: true, values: returnValues } }, - delete: diNotImplementedYet, + delete(resources: DIResources) { + const { collection: _collection, dataContext } = resources + if (!dataContext) return dataContextNotFoundResult + if (!_collection) return collectionNotFoundResult + const collectionId = _collection.id + // For now, it's only possible to delete a grouped collection. + const collection = dataContext.getGroupedCollection(collectionId) + if (!collection) return collectionNotFoundResult + + dataContext.applyModelChange(() => { + dataContext.removeCollectionWithAttributes(collection) + }) + + return { success: true, values: { collections: [toV2Id(collectionId)] } } + }, get(resources: DIResources) { const { collection, dataContext } = resources @@ -94,7 +108,22 @@ export const diCollectionHandler: DIHandler = { } }, - update: diNotImplementedYet + update(resources: DIResources, values?: DIValues) { + const { collection, dataContext } = resources + if (!dataContext) return dataContextNotFoundResult + if (!collection) return collectionNotFoundResult + + if (values) { + const { title, labels } = values as DIUpdateCollection + + dataContext.applyModelChange(() => { + if (title) collection.setTitle(title) + if (labels) collection.setLabels(labels) + }) + } + + return { success: true } + } } registerDIHandler("collection", diCollectionHandler) diff --git a/v3/src/models/data/collection.test.ts b/v3/src/models/data/collection.test.ts index 229f6b8d89..a28c710220 100644 --- a/v3/src/models/data/collection.test.ts +++ b/v3/src/models/data/collection.test.ts @@ -34,6 +34,46 @@ describe("CollectionModel", () => { expect(isCollectionModel(withNameAndTitle)).toBe(false) }) + it("labels work as expected", () => { + const c1 = CollectionPropsModel.create({ name: "c1" }) + expect(c1.labels).toBeUndefined() + c1.setSingleCase("singleCase") + expect(c1.labels?.singleCase).toBe("singleCase") + c1.setLabels({ + pluralCase: "pluralCase", + singleCaseWithArticle: "singleCaseWithArticle", + setOfCases: "setOfCases", + setOfCasesWithArticle: "setOfCasesWithArticle" + }) + expect(c1.labels?.singleCase).toBe("singleCase") + expect(c1.labels?.pluralCase).toBe("pluralCase") + expect(c1.labels?.singleCaseWithArticle).toBe("singleCaseWithArticle") + expect(c1.labels?.setOfCases).toBe("setOfCases") + expect(c1.labels?.setOfCasesWithArticle).toBe("setOfCasesWithArticle") + + const c2 = CollectionPropsModel.create({ name: "c2" }) + expect(c2.labels).toBeUndefined() + c2.setPluralCase("pluralCase") + expect(c2.labels?.pluralCase).toBe("pluralCase") + c2.setSingleCase("singleCase") + expect(c2.labels?.singleCase).toBe("singleCase") + + const c3 = CollectionPropsModel.create({ name: "c3" }) + expect(c3.labels).toBeUndefined() + c3.setSingleCaseWithArticle("singleCaseWithArticles") + expect(c3.labels?.singleCaseWithArticle).toBe("singleCaseWithArticles") + + const c4 = CollectionPropsModel.create({ name: "c4" }) + expect(c4.labels).toBeUndefined() + c4.setSetOfCases("setOfCases") + expect(c4.labels?.setOfCases).toBe("setOfCases") + + const c5 = CollectionPropsModel.create({ name: "c5" }) + expect(c5.labels).toBeUndefined() + c5.setSetOfCasesWithArticle("setOfCasesWithArticle") + expect(c5.labels?.setOfCasesWithArticle).toBe("setOfCasesWithArticle") + }) + it("handles undefined references", () => { const tree = Tree.create() const collection = CollectionModel.create() diff --git a/v3/src/models/data/collection.ts b/v3/src/models/data/collection.ts index 35da74e723..abfb41d65b 100644 --- a/v3/src/models/data/collection.ts +++ b/v3/src/models/data/collection.ts @@ -11,11 +11,58 @@ export const CollectionLabels = types.model("CollectionLabels", { setOfCases: "", setOfCasesWithArticle: "" }) +export interface ICollectionLabels extends Instance {} export const CollectionPropsModel = V2Model.named("CollectionProps").props({ id: typeV3Id(kCollectionIdPrefix), labels: types.maybe(CollectionLabels) }) +.actions(self => ({ + setSingleCase(singleCase: string) { + if (self.labels) { + self.labels.singleCase = singleCase + } else { + self.labels = CollectionLabels.create({ singleCase }) + } + }, + setPluralCase(pluralCase: string) { + if (self.labels) { + self.labels.pluralCase = pluralCase + } else { + self.labels = CollectionLabels.create({ pluralCase }) + } + }, + setSingleCaseWithArticle(singleCaseWithArticle: string) { + if (self.labels) { + self.labels.singleCaseWithArticle = singleCaseWithArticle + } else { + self.labels = CollectionLabels.create({ singleCaseWithArticle }) + } + }, + setSetOfCases(setOfCases: string) { + if (self.labels) { + self.labels.setOfCases = setOfCases + } else { + self.labels = CollectionLabels.create({ setOfCases }) + } + }, + setSetOfCasesWithArticle(setOfCasesWithArticle: string) { + if (self.labels) { + self.labels.setOfCasesWithArticle = setOfCasesWithArticle + } else { + self.labels = CollectionLabels.create({ setOfCasesWithArticle }) + } + } +})) +.actions(self => ({ + setLabels(labels: Partial) { + if (labels.singleCase) self.setSingleCase(labels.singleCase) + if (labels.pluralCase) self.setPluralCase(labels.pluralCase) + if (labels.singleCaseWithArticle) self.setSingleCaseWithArticle(labels.singleCaseWithArticle) + if (labels.setOfCases) self.setSetOfCases(labels.setOfCases) + if (labels.setOfCasesWithArticle) self.setSetOfCasesWithArticle(labels.setOfCasesWithArticle) + } +})) export interface ICollectionPropsModel extends Instance {} export const CollectionModel = CollectionPropsModel diff --git a/v3/src/models/data/data-set.ts b/v3/src/models/data/data-set.ts index 2dd266fadc..2ac712ffd5 100644 --- a/v3/src/models/data/data-set.ts +++ b/v3/src/models/data/data-set.ts @@ -1140,6 +1140,15 @@ export const DataSet = V2Model.named("DataSet").props({ commitCache && this.commitCache() self.clearCache() } + }, + removeCollectionWithAttributes(collection: ICollectionModel) { + collection.attributes.forEach(attribute => { + if (attribute) { + collection.removeAttribute(attribute.id) + self.removeAttribute(attribute.id) + } + }) + self.removeCollection(collection) } })) // performs the specified action so that response actions are included and undo/redo strings assigned