Skip to content

Commit

Permalink
Merge pull request #1498 from concord-consortium/187799307-filter-fun…
Browse files Browse the repository at this point in the history
…ction

Add support for the filter function
  • Loading branch information
pjanik authored Sep 24, 2024
2 parents f107127 + a85ef8c commit 8bdd361
Show file tree
Hide file tree
Showing 12 changed files with 510 additions and 45 deletions.
2 changes: 1 addition & 1 deletion v3/src/components/case-table/use-rows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const useRows = () => {

const onPatchDisposer = data && onPatch(data, ({ op, path, value }) => {
// reset on any changes to items or hidden items
if (/(_itemIds|hiddenItemIds)(\/\d+)?$/.test(path)) {
if (/(_itemIds|setAsideItemIds)(\/\d+)?$/.test(path)) {
resetRowCacheAndSyncRows()
}
})
Expand Down
95 changes: 95 additions & 0 deletions v3/src/components/case-tile-common/edit-filter-formula-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
Button, FormControl, FormLabel, ModalBody, ModalCloseButton, ModalFooter, ModalHeader,
Textarea, Tooltip
} from "@chakra-ui/react"
import React, { useEffect, useState } from "react"
import { observer } from "mobx-react-lite"
import { useDataSetContext } from "../../hooks/use-data-set-context"
import { t } from "../../utilities/translation/translate"
import { CodapModal } from "../codap-modal"

interface IProps {
isOpen: boolean
onClose: () => void
}

export const EditFilterFormulaModal = observer(function EditFormulaModal({ isOpen, onClose }: IProps) {
const data = useDataSetContext()
const [formula, setFormula] = useState("")

useEffect(() => {
setFormula(data?.filterFormula?.display || "")
}, [data?.filterFormula?.display])

const closeModal = () => {
onClose()
}

function applyFilterFormula() {
data?.applyModelChange(() => {
data.setFilterFormula(formula)
}, {
undoStringKey: "V3.Undo.hideShowMenu.changeFilterFormula",
redoStringKey: "V3.Redo.hideShowMenu.changeFilterFormula"
})
closeModal()
}

const handleFormulaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => setFormula(e.target.value)

const buttons = [{
label: t("DG.AttrFormView.cancelBtnTitle"),
tooltip: t("DG.AttrFormView.cancelBtnTooltip"),
onClick: closeModal
}, {
label: t("DG.AttrFormView.applyBtnTitle"),
onClick: applyFilterFormula,
default: true
}]

return (
<CodapModal
isOpen={isOpen}
onClose={onClose}
modalWidth={"400px"}
modalHeight={"180px"}
>
<ModalHeader h="30" className="codap-modal-header" fontSize="md" data-testid="codap-modal-header">
<div className="codap-modal-icon-container" />
<div className="codap-header-title" />
<ModalCloseButton onClick={onClose} data-testid="modal-close-button" />
</ModalHeader>
<ModalBody>
{
data?.filterFormulaError &&
<div className="formula-error" style={{ background: "rgb(254, 224, 228)", fontSize: "0.8em" }}>
{data.filterFormulaError}
</div>
}
<FormControl display="flex" flexDirection="column" className="formula-form-control">
<FormLabel>{t("DG.AttrFormView.formulaPrompt")}
<Textarea size="xs" value={formula} onChange={handleFormulaChange}
onKeyDown={(e) => e.stopPropagation()} data-testid="attr-formula-input" />
</FormLabel>
</FormControl>
</ModalBody>
<ModalFooter mt="-5">
{
buttons.map((b, idx) => {
const key = `${idx}-${b.label}`
return (
<Tooltip key={idx} label={b.tooltip} h="20px" fontSize="12px" color="white" openDelay={1000}
placement="bottom" bottom="15px" left="15px" data-testid="modal-tooltip"
>
<Button key={key} size="xs" variant={`${b.default ? "default" : ""}`} ml="5" onClick={b.onClick}
_hover={{backgroundColor: "#72bfca", color: "white"}} data-testid={`${b.label}-button`}>
{b.label}
</Button>
</Tooltip>
)
})
}
</ModalFooter>
</CodapModal>
)
})
29 changes: 25 additions & 4 deletions v3/src/components/case-tile-common/hide-show-menu-list.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { useDisclosure } from "@chakra-ui/react"
import { observer } from "mobx-react-lite"
import React from "react"
import { useCaseMetadata } from "../../hooks/use-case-metadata"
import { useDataSetContext } from "../../hooks/use-data-set-context"
import { hideAttributeNotification } from "../../models/data/data-set-notifications"
import { t } from "../../utilities/translation/translate"
import { EditFilterFormulaModal } from "./edit-filter-formula-modal"
import { IMenuItem, StdMenuList } from "./std-menu-list"

export const HideShowMenuList = observer(function HideShowMenuList() {
const data = useDataSetContext()
const caseMetadata = useCaseMetadata()
const formulaModal = useDisclosure()

const handleSetAsideCases = (itemIds: string[], deselect: boolean) => {
if (data && itemIds.length) {
Expand All @@ -22,9 +25,17 @@ export const HideShowMenuList = observer(function HideShowMenuList() {
}
}

const handleEditFormulaOpen = () => {
formulaModal.onOpen()
}

const handleEditFormulaClose = () => {
formulaModal.onClose()
}

const itemCount = data?.items.length ?? 0
const selectionCount = data?.selection.size ?? 0
const setAsideCount = data?.hiddenItemIds.length ?? 0
const setAsideCount = data?.setAsideItemIds.length ?? 0

const hiddenAttributes = data?.attributes.filter(attr => attr && caseMetadata?.isHidden(attr.id))
const hiddenAttributeCount = hiddenAttributes?.length ?? 0
Expand Down Expand Up @@ -54,9 +65,9 @@ export const HideShowMenuList = observer(function HideShowMenuList() {
itemLabel: () => t("DG.Inspector.setaside.restoreSetAsideCases", { vars: [setAsideCount] }),
isEnabled: () => setAsideCount > 0,
handleClick: () => {
if (data?.hiddenItemIds.length) {
if (data?.setAsideItemIds.length) {
data.applyModelChange(() => {
const hiddenItems = [...data.hiddenItemIds]
const hiddenItems = [...data.setAsideItemIds]
data.showHiddenCasesAndItems()
data.setSelectedCases(hiddenItems)
}, {
Expand All @@ -67,6 +78,13 @@ export const HideShowMenuList = observer(function HideShowMenuList() {
}
}
},
{
itemKey: !data?.filterFormula?.empty
? "V3.hideShowMenu.editFilterFormula"
: "V3.hideShowMenu.addFilterFormula",
isEnabled: () => !!data,
handleClick: handleEditFormulaOpen
},
{
itemKey: "DG.Inspector.attributes.showAllHiddenAttributesPlural",
isEnabled: () => hiddenAttributeCount > 0,
Expand Down Expand Up @@ -98,6 +116,9 @@ export const HideShowMenuList = observer(function HideShowMenuList() {
]

return (
<StdMenuList data-testid="hide-show-menu-list" menuItems={menuItems} />
<>
<StdMenuList data-testid="hide-show-menu-list" menuItems={menuItems} />
<EditFilterFormulaModal isOpen={formulaModal.isOpen} onClose={handleEditFormulaClose} />
</>
)
})
2 changes: 1 addition & 1 deletion v3/src/models/codap/create-codap-document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe("createCodapDocument", () => {
}
},
_itemIds: ["test-9", "test-10", "test-11"],
hiddenItemIds: [],
setAsideItemIds: [],
collections: [{
id: "test-7",
name: "Cases",
Expand Down
24 changes: 23 additions & 1 deletion v3/src/models/data/data-set-conversion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IAttributeSnapshot } from "./attribute"
import { ICollectionModelSnapshot } from "./collection"
import { DataSet } from "./data-set"
import {
IInitialItemsDataSetSnap, IOriginalDataSetSnap, IPreItemsDataSetSnap, ITempDataSetSnap
IHiddenItemIdsDataSetSnap, IInitialItemsDataSetSnap, IOriginalDataSetSnap, IPreItemsDataSetSnap, ITempDataSetSnap
} from "./data-set-conversion"

const kUngroupedCollectionName = "Collection Formerly Known As Ungrouped"
Expand Down Expand Up @@ -109,4 +109,26 @@ describe("DataSet conversions", () => {
expect(data._itemIds.length).toBe(1)
expect(data.itemIds.length).toBe(1)
})

test("DataSet hiddenItemIds flat snapshot conversion", () => {
const data = DataSet.create({
name: "Data",
collections: [{
name: "Cases",
attributes: ["a1Id", "a2Id", "a3Id"]
}],
attributesMap: {
a1Id: { id: "a1Id", name: "a1", values: ["a1-0"] },
a2Id: { id: "a2Id", name: "a2", values: ["a2-0"] },
a3Id: { id: "a3Id", name: "a3", values: ["a3-0"] }
},
_itemIds: ["ITEM0"],
hiddenItemIds: ["ITEM0"]
} as IHiddenItemIdsDataSetSnap)
expect(data.attributesMap.size).toBe(3)
expect(data.attributes.length).toBe(3)
expect(data._itemIds.length).toBe(1)
expect(data.itemIds.length).toBe(1)
expect(data.setAsideItemIds.length).toBe(1)
})
})
16 changes: 12 additions & 4 deletions v3/src/models/data/data-set-conversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,19 @@ export function isInitialItemsDataSetSnap(snap: IDataSetSnapshot): snap is IInit
return !("attributes" in snap) && !("ungrouped" in snap) && !("cases" in snap) && ("itemIds" in snap)
}

export type ILegacyDataSetSnap = (IOriginalDataSetSnap | ITempDataSetSnap |
IPreItemsDataSetSnap | IInitialItemsDataSetSnap) & { itemIds?: string[] }
// hiddenItemIds => setAsideItemIds
export interface IHiddenItemIdsDataSetSnap extends Omit<IInitialItemsDataSetSnap, "itemIds"> {
hiddenItemIds?: string[]
}
export function isHiddenItemIdsDataSetSnap(snap: IDataSetSnapshot): snap is IHiddenItemIdsDataSetSnap {
return "hiddenItemIds" in snap
}

export type ILegacyDataSetSnap = (IOriginalDataSetSnap | ITempDataSetSnap | IPreItemsDataSetSnap |
IInitialItemsDataSetSnap | IHiddenItemIdsDataSetSnap) & { itemIds?: string[], hiddenItemIds?: string[] }
export function isLegacyDataSetSnap(snap: IDataSetSnapshot): snap is ILegacyDataSetSnap {
return isOriginalDataSetSnap(snap) || isTempDataSetSnap(snap) ||
isPreItemDataSetSnap(snap) || isInitialItemsDataSetSnap(snap)
return isOriginalDataSetSnap(snap) || isTempDataSetSnap(snap) || isPreItemDataSetSnap(snap) ||
isInitialItemsDataSetSnap(snap) || isHiddenItemIdsDataSetSnap(snap)
}

export function createDataSet(snap: IDataSetSnapshot | ILegacyDataSetSnap): IDataSet {
Expand Down
Loading

0 comments on commit 8bdd361

Please sign in to comment.