Skip to content

Commit

Permalink
187573958 v3 Fix Sampler (#1251)
Browse files Browse the repository at this point in the history
* Handle get collection requests.

* Handle create item and get attribtueList requests.

* Handle create caseTable component requests.
  • Loading branch information
tealefristoe authored May 15, 2024
1 parent 9b547c4 commit 912e18a
Show file tree
Hide file tree
Showing 24 changed files with 484 additions and 194 deletions.
69 changes: 5 additions & 64 deletions v3/src/components/case-table/case-table-tool-shelf-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,18 @@ import { Button, Menu, MenuButton, MenuItem, MenuList, ModalBody, ModalFooter,
import { observer } from "mobx-react-lite"
import { t } from "../../utilities/translation/translate"
import { getFormulaManager, getSharedModelManager } from "../../models/tiles/tile-environment"
import { getTileComponentInfo } from "../../models/tiles/tile-component-info"
import { kTitleBarHeight } from "../constants"
import { ComponentRect } from "../../utilities/animation-utils"
import { appState } from "../../models/app-state"
import { ISharedDataSet, kSharedDataSetType, SharedDataSet } from "../../models/shared/shared-data-set"
import { ISharedCaseMetadata, kSharedCaseMetadataType, SharedCaseMetadata }
from "../../models/shared/shared-case-metadata"
import { kSharedDataSetType, SharedDataSet } from "../../models/shared/shared-data-set"
import { DataSet, toCanonical } from "../../models/data/data-set"
import { gDataBroker } from "../../models/data/data-broker"
import { isFreeTileLayout } from "../../models/document/free-tile-row"
import { createDefaultTileOfType } from "../../models/codap/add-default-content"
import { kCaseTableTileType } from "./case-table-defs"
import { getPositionOfNewComponent } from "../../utilities/view-utils"
import { CodapModal } from "../codap-modal"
import { uiState } from "../../models/ui-state"
import TableIcon from "../../assets/icons/icon-table.svg"
import TrashIcon from "../../assets/icons/icon-trash.svg"
import AlertIcon from "../../assets/icons/icon-alert.svg"
import { ToolShelfButtonTag } from "../tool-shelf/tool-shelf-button"
import { createOrShowTableForDataset, createTableForDataset } from "./case-table-utils"

import "../tool-shelf/tool-shelf.scss"

Expand All @@ -31,46 +24,11 @@ export const CaseTableToolShelfMenuList = observer(function CaseTableToolShelfMe
const content = document.content
const manager = getSharedModelManager(document)
const datasets = manager?.getSharedModelsByType<typeof SharedDataSet>(kSharedDataSetType)
const caseMetadatas = manager?.getSharedModelsByType<typeof SharedCaseMetadata>(kSharedCaseMetadataType)
const row = content?.getRowByIndex(0)
const { isOpen, onOpen, onClose } = useDisclosure()
const [modalOpen, setModalOpen] = useState(false)
const [dataSetIdToDeleteId, setDataSetIdToDelete] = useState("")

if (!row || !content) return null

const openTableForDataset = (model: ISharedDataSet, caseMetadata: ISharedCaseMetadata) => {
const caseTableTileId = caseMetadata.caseTableTileId
const caseTableComponentInfo = getTileComponentInfo(kCaseTableTileType)
if (!caseTableComponentInfo) return
if (caseTableTileId) {
content?.toggleNonDestroyableTileVisibility(caseTableTileId)
return
}
const tile = createDefaultTileOfType(kCaseTableTileType)
if (!tile) return
manager?.addTileSharedModel(tile.content, model, true)
manager?.addTileSharedModel(tile.content, caseMetadata, true)
caseMetadata.setCaseTableTileId(tile.id)

const width = caseTableComponentInfo.defaultWidth || 0
const height = caseTableComponentInfo.defaultHeight || 0
const {x, y} = getPositionOfNewComponent({width, height})
const from: ComponentRect = { x: 0, y: 0, width: 0, height: kTitleBarHeight },
to: ComponentRect = { x, y, width, height: height + kTitleBarHeight}
content?.insertTileInRow(tile, row, from)
uiState.setFocusedTile(tile.id)
const tileLayout = content.getTileLayoutById(tile.id)
if (!isFreeTileLayout(tileLayout)) return
// use setTimeout to push the change into a subsequent action
setTimeout(() => {
// use applyModelChange to wrap into a single non-undoable action without undo string
content.applyModelChange(() => {
tileLayout.setPosition(to.x, to.y)
tileLayout.setSize(to.width, to.height)
})
})
}
if (!content) return null

const handleCreateNewDataSet = () => {
document.applyModelChange(() => {
Expand All @@ -83,30 +41,13 @@ export const CaseTableToolShelfMenuList = observer(function CaseTableToolShelfMe
const { sharedData, caseMetadata } = gDataBroker.addDataSet(ds, tile.id)
// Add dataset to the formula manager
getFormulaManager(document)?.addDataSet(ds)
openTableForDataset(sharedData, caseMetadata)
createTableForDataset(sharedData, caseMetadata)
}, {
undoStringKey: "V3.Undo.caseTable.create",
redoStringKey: "V3.Redo.caseTable.create"
})
}

const handleOpenDataSetTable = (dataset: ISharedDataSet) => {
const model = manager?.getSharedModelsByType("SharedDataSet")
.find(m => m.id === dataset.id) as ISharedDataSet | undefined
const caseMetadata = caseMetadatas?.find(cm => cm.data?.id === model?.dataSet.id)
if (!model || !caseMetadata) return
const existingTileId = caseMetadata.lastShownTableOrCardTileId
if (existingTileId) { // We already have a case table so make sure it's visible and has focus
const existingTile = content.getTileLayoutById(existingTileId)
if (isFreeTileLayout(existingTile) && existingTile.isHidden) {
content?.toggleNonDestroyableTileVisibility(existingTileId)
}
uiState.setFocusedTile(existingTileId)
} else { // We don't already have a table for this dataset
openTableForDataset(model, caseMetadata)
}
}

const handleOpenRemoveDataSetModal = (dsId: string) => {
setModalOpen(true)
onOpen()
Expand All @@ -119,7 +60,7 @@ export const CaseTableToolShelfMenuList = observer(function CaseTableToolShelfMe
// case table title reflects DataSet title
const tileTitle = dataset.dataSet.title
return (
<MenuItem key={`${dataset.dataSet.id}`} onClick={()=>handleOpenDataSetTable(dataset)}
<MenuItem key={`${dataset.dataSet.id}`} onClick={()=>createOrShowTableForDataset(dataset)}
data-testid={`tool-shelf-table-${tileTitle}`}>
{tileTitle}
<TrashIcon className="tool-shelf-menu-trash-icon"
Expand Down
78 changes: 78 additions & 0 deletions v3/src/components/case-table/case-table-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { appState } from "../../models/app-state"
import { createDefaultTileOfType } from "../../models/codap/add-default-content"
import { isFreeTileLayout } from "../../models/document/free-tile-row"
import {
ISharedCaseMetadata, kSharedCaseMetadataType, SharedCaseMetadata
} from "../../models/shared/shared-case-metadata"
import { ISharedDataSet } from "../../models/shared/shared-data-set"
import { getTileComponentInfo } from "../../models/tiles/tile-component-info"
import { getSharedModelManager } from "../../models/tiles/tile-environment"
import { uiState } from "../../models/ui-state"
import { ComponentRect } from "../../utilities/animation-utils"
import { getPositionOfNewComponent } from "../../utilities/view-utils"
import { kTitleBarHeight } from "../constants"
import { kCaseTableTileType } from "./case-table-defs"

export const createTableForDataset = (model: ISharedDataSet, caseMetadata: ISharedCaseMetadata) => {
const document = appState.document
const { content } = document
const row = content?.getRowByIndex(0)
const manager = getSharedModelManager(document)
const caseTableComponentInfo = getTileComponentInfo(kCaseTableTileType)
if (!content || !row || !caseTableComponentInfo) return

const caseTableTileId = caseMetadata.caseTableTileId
if (caseTableTileId) {
content?.toggleNonDestroyableTileVisibility(caseTableTileId)
return
}

const tile = createDefaultTileOfType(kCaseTableTileType)
if (!tile) return

manager?.addTileSharedModel(tile.content, model, true)
manager?.addTileSharedModel(tile.content, caseMetadata, true)
caseMetadata.setCaseTableTileId(tile.id)

const width = caseTableComponentInfo.defaultWidth || 0
const height = caseTableComponentInfo.defaultHeight || 0
const {x, y} = getPositionOfNewComponent({width, height})
const from: ComponentRect = { x: 0, y: 0, width: 0, height: kTitleBarHeight },
to: ComponentRect = { x, y, width, height: height + kTitleBarHeight}
content?.insertTileInRow(tile, row, from)
uiState.setFocusedTile(tile.id)
const tileLayout = content.getTileLayoutById(tile.id)
if (!isFreeTileLayout(tileLayout)) return
// use setTimeout to push the change into a subsequent action
setTimeout(() => {
// use applyModelChange to wrap into a single non-undoable action without undo string
content.applyModelChange(() => {
tileLayout.setPosition(to.x, to.y)
tileLayout.setSize(to.width, to.height)
})
})

return tile
}

export const createOrShowTableForDataset = (sharedDataset: ISharedDataSet) => {
const document = appState.document
const { content } = document
const manager = getSharedModelManager(document)
const caseMetadatas = manager?.getSharedModelsByType<typeof SharedCaseMetadata>(kSharedCaseMetadataType)

const model = manager?.getSharedModelsByType("SharedDataSet")
.find(m => m.id === sharedDataset.id) as ISharedDataSet | undefined
const caseMetadata = caseMetadatas?.find(cm => cm.data?.id === model?.dataSet.id)
if (!model || !caseMetadata) return
const existingTileId = caseMetadata.lastShownTableOrCardTileId
if (existingTileId) { // We already have a case table so make sure it's visible and has focus
if (content?.isTileHidden(existingTileId)) {
content?.toggleNonDestroyableTileVisibility(existingTileId)
}
uiState.setFocusedTile(existingTileId)
return content?.tileMap.get(existingTileId)
} else { // We don't already have a table for this dataset
return createTableForDataset(model, caseMetadata)
}
}
17 changes: 17 additions & 0 deletions v3/src/data-interactive/data-interactive-component-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,20 @@ export const kComponentTypeV3ToV2Map: Record<string, string> = {
// kV2TextType
[kWebViewTileType]: kV2WebViewType
}

export interface V2CaseTable {
type: "caseTable"
name?: string
title?: string
dimensions?: {
width: number
height: number
}
position?: string
cannotClose?: boolean
dataContext?: string
horizontalScrollOffset?: number
isIndexHidden?: boolean
}

export type V2Component = V2CaseTable
5 changes: 5 additions & 0 deletions v3/src/data-interactive/data-interactive-type-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,8 @@ export function basicDataSetInfo(dataSet: IDataSet) {
title: dataSet.title
}
}

export function basicAttributeInfo(attribute: IAttribute) {
const { name, id, title } = attribute
return { name, id, title }
}
7 changes: 5 additions & 2 deletions v3/src/data-interactive/data-interactive-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { IDataSet } from "../models/data/data-set"
import { IGlobalValue } from "../models/global/global-value"
import { ITileModel } from "../models/tiles/tile-model"
import { ICollectionPropsModel } from "../models/data/collection"
import { V2Component } from "./data-interactive-component-types"

export type DICaseValue = string | number | boolean | undefined
export type DICaseValues = Record<string, DICaseValue>
Expand Down Expand Up @@ -73,7 +74,7 @@ export interface DIInteractiveFrame {
title?: string
version?: string
}
export type DIItem = unknown
export type DIItem = DICaseValues
export interface DINewCase {
id?: number
itemID?: number
Expand All @@ -84,6 +85,7 @@ export interface DINotification {

export interface DIResources {
attribute?: IAttribute
attributeList?: IAttribute[]
attributeLocation?: IAttribute
caseByID?: DICase
caseByIndex?: DICase
Expand All @@ -105,7 +107,7 @@ export interface DIResources {

// types for values accepted as inputs by the API
export type DISingleValues = DIAttribute | DICase | DIDataContext |
DIGlobal | DIInteractiveFrame | DINewCase | DINotification
DIGlobal | DIInteractiveFrame | DINewCase | DINotification | V2Component
export type DIValues = DISingleValues | DISingleValues[] | number | string[]

// types returned as outputs by the API
Expand All @@ -122,6 +124,7 @@ export interface DISuccessResult {
success: true
values?: DIResultValues
caseIDs?: number[]
itemIDs?: string[]
}

export interface DIErrorResult {
Expand Down
11 changes: 11 additions & 0 deletions v3/src/data-interactive/data-interactive-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IDataSet } from "../models/data/data-set"
import { ICaseCreation } from "../models/data/data-set-types"
import { DICaseValues } from "./data-interactive-types"

export function canonicalizeAttributeName(name: string, iCanonicalize = true) {
Expand Down Expand Up @@ -32,3 +33,13 @@ export function getCaseValues(caseId: string, collectionId: string, dataSet: IDa

return values
}

// Converts an attributeName => value dictionary to attributeId => value
export function attrNamesToIds(values: DICaseValues, dataSet: IDataSet) {
const caseValues: ICaseCreation = {}
Object.keys(values).forEach(attrName => {
const attrId = dataSet.attrIDFromName(attrName)
if (attrId) caseValues[attrId] = values[attrName]
})
return caseValues
}
34 changes: 5 additions & 29 deletions v3/src/data-interactive/handlers/all-cases-handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,18 @@
import { CollectionModel, ICollectionModel } from "../../models/data/collection"
import { DataSet, IDataSet, toCanonical } from "../../models/data/data-set"
import { DIAllCases } from "../data-interactive-types"
import { diAllCasesHandler } from "./all-cases-handler"
import { setupTestDataset, testCases } from "./handler-test-utils"

describe("DataInteractive AllCasesHandler", () => {
const handler = diAllCasesHandler

let dataset: IDataSet | undefined
let c1: ICollectionModel | undefined
let c2: ICollectionModel | undefined
const cases = [
{ a1: "a", a2: "x", a3: 1 },
{ a1: "b", a2: "y", a3: 2 },
{ a1: "a", a2: "z", a3: 3 },
{ a1: "b", a2: "z", a3: 4 },
{ a1: "a", a2: "x", a3: 5 },
{ a1: "b", a2: "y", a3: 6 },
]
const setupDataset = () => {
dataset = DataSet.create({ name: "data" })
c1 = CollectionModel.create({ name: "collection1" })
c2 = CollectionModel.create({ name: "collection2" })
dataset.addCollection(c1)
dataset.addCollection(c2)
dataset.addAttribute({ name: "a1" }, { collection: c1.id })
dataset.addAttribute({ name: "a2" }, { collection: c2.id })
dataset.addAttribute({ name: "a3" })
dataset.addCases(toCanonical(dataset, cases))
}

it("delete works as expected", () => {
setupDataset()
expect(dataset!.cases.length).toBe(cases.length)
const { dataset } = setupTestDataset()
expect(dataset.cases.length).toBe(testCases.length)
expect(handler.delete?.({ dataContext: dataset })?.success).toBe(true)
expect(dataset!.cases.length).toBe(0)
expect(dataset.cases.length).toBe(0)
})
it("get works as expected", () => {
setupDataset()
const { dataset, c1, c2 } = setupTestDataset()

interface GetAllCasesResult {
success: boolean,
Expand Down
26 changes: 26 additions & 0 deletions v3/src/data-interactive/handlers/attribute-list-handler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ICodapV2Attribute } from "../../v2/codap-v2-types"
import { diAttributeListHandler } from "./attribute-list-handler"
import { setupTestDataset } from "./handler-test-utils"

describe("DataInteractive AttributeListHandler", () => {
const handler = diAttributeListHandler

it("get works as expected", () => {
const { a1, a2 } = setupTestDataset()

expect(handler.get?.({})?.success).toBe(false)

const result = handler.get?.({ attributeList: [a1, a2] })
expect(result?.success).toBe(true)
const attributeList = result?.values as Partial<ICodapV2Attribute>[]
expect(attributeList.length).toBe(2)
const attr1 = attributeList[0]
expect(attr1.name).toBe(a1.name)
expect(attr1.title).toBe(a1.title)
expect(attr1.id).toBe(a1.id)
const attr2 = attributeList[1]
expect(attr2.name).toBe(a2.name)
expect(attr2.title).toBe(a2.title)
expect(attr2.id).toBe(a2.id)
})
})
18 changes: 18 additions & 0 deletions v3/src/data-interactive/handlers/attribute-list-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { t } from "../../utilities/translation/translate"
import { registerDIHandler } from "../data-interactive-handler"
import { basicAttributeInfo } from "../data-interactive-type-utils"
import { DIHandler, DIResources } from "../data-interactive-types"

export const diAttributeListHandler: DIHandler = {
get(resources: DIResources) {
const { attributeList } = resources
if (!attributeList) return { success: false, values: { error: t("V3.DI.Error.collectionNotFound") } }

return {
success: true,
values: attributeList.map(attribute => basicAttributeInfo(attribute))
}
}
}

registerDIHandler("attributeList", diAttributeListHandler)
Loading

0 comments on commit 912e18a

Please sign in to comment.