From d920afcc5186beedec2d1bf42f4ba4db486b1bf9 Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal Date: Mon, 24 Aug 2026 17:05:06 -0400 Subject: [PATCH 01/12] update cohort info --- .../Users_Guide/Developers_Guide.md | 483 ++++++++++-------- 1 file changed, 263 insertions(+), 220 deletions(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index 7b9726a16..adae9080f 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -11,13 +11,13 @@ The GDC Data Portal is built on top of the [GDC API](https://docs.gdc.cancer.gov which provides access to the GDC data. The GDC Data Portal provides an Analysis Tool Framework (ATF) for developing applications that can be used to analyze, visualize, and download data from the GDC. -The GDC Data Portal is built with the [React](https://reactjs.org/) framework and -the [Redux](https://redux.js.org/) library for state management. The GDC Data Portal uses [NextJS](https://nextjs.org/) as its application framework which +The GDC Data Portal is built with the [React](https://reactjs.org/) framework and +the [Redux](https://redux.js.org/) library for state management. The GDC Data Portal uses [NextJS](https://nextjs.org/) as its application framework which provides server-side rendering of React components. [Mantine.dev](https://mantine.dev/) is the component library, and -styling is through [TailwindCSS](https://tailwindcss.com/). The GDC Data Portal is built on top of the GDC API, which provides access to +styling is through [TailwindCSS](https://tailwindcss.com/). The GDC Data Portal is built on top of the GDC API, which provides access to the GDC data. -![This image details the architecture of the GDC Data Portal. +![This image details the architecture of the GDC Data Portal. It shows the interaction between the GDC Data API, the core module and the user interface.](./images/developers_guide/V2_architecture.png "Architecture of the GDC Data Portal") @@ -28,7 +28,7 @@ module and the user interface.](./images/developers_guide/V2_architecture.png "A Applications are React higher-order components (HOC) that are rendered in the [Analysis Center](https://portal.gdc.cancer.gov/analysis_page?app=). The GDC Data Portal's major functions such as Projects, Repository, and ProteinPaint are all applications. Each application handles a specific task such as analysis or -visualization and can also be used to refine and build cohorts. Applications are cohort centric and can +visualization and can also be used to refine and build cohorts. Applications are cohort centric and can query the GDC API for additional information. Local and Global filters are available to applications. Local filters are filters that are specific to the application @@ -40,8 +40,8 @@ refine the input cohort allowing users to drill down to specific genes and mutat ### Local vs Global Filters -The GDC Data Portal application's input can be the current cohort or multiple user defined cohorts. The application then -allow users to add filters refining the cohort, create new additional cohorts, or display the data in a visualization. +The GDC Data Portal application's input can be the current cohort or multiple user defined cohorts. The application then +allow users to add filters refining the cohort, create new additional cohorts, or display the data in a visualization. Applications typically have: * **Local filters** Refine the data displayed in the application @@ -57,34 +57,46 @@ below illustrates the application components and cohort filters. ## Cohorts and Filters +A cohort is a named set of filters. Counts, case lists and tables are derived by re-running those filters against the GDC API. From an application perspective, a cohort is an Object containing the following information: ```typescript interface Cohort { - id: string; // unique id for cohort - name: string; // name of cohort - filters: FilterSet; // active filters for cohort - caseSet: CaseSetDataAndStatus; // case ids that are in the cohort - modified?: boolean; // flag which is set to true is modified and unsaved - modified_datetime: string; // last time cohort was modified - saved?: boolean; // flag indicating if cohort has been saved. - counts: CountsDataAndStatus; //case, file, etc. counts of a cohort + readonly id: string; // unique id for cohort + readonly name: string; // name of cohort + readonly filters: FilterSet; // active filters for cohort + readonly caseSet: CaseSetDataAndStatus; // case set ids, for frozen cohorts + readonly counts: CountsDataAndStatus; //case, file, gene, mutation counts + readonly modified_datetime: string; // last time cohort was modified + readonly modified?: boolean; // true if modified and unsaved + readonly saved?: boolean; // true once persisted to the GDC API + readonly unsavedCohortId?: string; // prior local id, retained after saving + readonly deprecatedFields?: string[]; // filter fields no longer in the data model + readonly nonexistentFields?: string[]; // filter fields the API does not recognise + readonly removed?: true; // set when the user deletes the cohort } ``` -Likely the most important part of the cohort is the `filters` field. The `filters` field contains the active filters for -the cohort. The `filters` field is a `FilterSet` object. The `FilterSet` object contains the active filters for the -cohort. When calling either the GDC REST API or GDC GraphQL API the `FilterSet` is converted to the appropriate format -for the API. The `FilterSet` object is of the form: +Deleting a cohort sets `removed: true` rather than removing it, so applications holding its id can still resolve it. +Saving a cohort creates a new entity under the server-issued id; `unsavedCohortId` points back at the local id it replaced. + +### FilterSet ```typescript interface FilterSet { - op: "and" | "or"; // operator for combining filters - root: Record; // map of filter name to filter operation + readonly root: Record; // map of field name to filter operation + readonly mode: string; // root-level combining operator for filters (e.g., and | or) } ``` -Operation are GDC API filters as described in +`root` is keyed by field name, so a cohort holds at most one operation per field. An empty `root` means all of the GDC. + + +```typescript +const allOfGDC: FilterSet = { mode: "and", root: {} }; +``` + +Operations are GDC API filters as described in the [GDC API](https://docs.gdc.cancer.gov/API/Users_Guide/Search_and_Retrieval/#filters-specifying-the-query). These are: @@ -102,67 +114,81 @@ are: * Intersection * Union -The `root` field is a map of filter names (as defined in the GDC API) to filter operation. The filter operation can be -either a single operation or a `FilterSet` object. The `op` field will eventually support either `and` or `or`, however -at this time only `and` is supported. The `and` operator is used to combine filters using the `and` operator. The `or` -operator is used to combine filters using the `or` operator. The `FilterSet` object is converted to the appropriate -format for the GDC API when the cohort is saved. +### Converting filters for the API + +A `FilterSet` is never sent to the API as-is. + +| Target | Function | +|---|---| +| GraphQL API, and REST `filters` / `case_filters` | `buildCohortGqlOperator(filterSet)` | +| A single `Operation` tree | `filterSetToOperation(filterSet)` | +| API filter back into a `FilterSet` | `buildGqlOperationToFilterSet(gqlOperation)` | + +```typescript +import { buildCohortGqlOperator, useCurrentCohortFilters } from "@gff/core"; + +const cohortFilters = useCurrentCohortFilters(); +const gqlFilters = buildCohortGqlOperator(cohortFilters); +``` + +`buildCohortGqlOperator` returns `undefined` for an empty filter set. + +To combine cohort filters with an application's local filters, use `joinFilters`: + +```typescript +import { joinFilters } from "@gff/core"; + +const combined = joinFilters(cohortFilters, localFilters); +``` + +`joinFilters` is a shallow merge of `root` and the second argument wins. -When using the GDC REpresentational State Transfer (REST) API, the FilterSet can be converted into the appropriate -format using the `filterSetToOperation` function. When using the GDC GraphQL API, the FilterSet can be using the -`convertFilterSetToGraphQL` function. The API guide will provide information on what format the filters should be in for the API. Also as the code is in TypeScript, -the IDE will provide information on the format as well. ### Obtaining Cohort Information -The current active cohort can be accessed via the selector `selectCurrentCohort`. This selector returns the current -cohort, which is the cohort that is currently being displayed in the Cohort Management Bar. Accessing the current cohort -is done via the -selector: +The current cohort is the one displayed in the Cohort Management Bar: ```typescript import {useCoreSelector, selectCurrentCohort} from '@gff/core'; -const currentCohort = useSelector(selectCurrentCohort); +const currentCohort = useCoreSelector((state) => selectCurrentCohort(state)); ``` -By using the selector, the component/application will be updated when the cohort changes. There are also selectors for -getting a particular field from the cohort. For example, to get the cohort name, the selector `selectCurrentCohortName` -can be used. The selectors are: - -* `selectCurrentCohort` -* `selectCurrentCohortName` -* `selectCurrentCohortId` -* `selectCurrentCohortFilters` -* `selectCurrentCohortModified` -* `selectCurrentCohortModifiedDatetime` -* `selectCurrentCohortSaved` -* `selectCurrentCohortCounts` - -The current active filters can be accessed via the selector `selectCurrentCohortFilters`. This selector returns the -current filters, -which are the filters that are currently being displayed in the Cohort Management Bar. Accessing the current filters is -done via the -selector: +| Selector | Returns | +|---|---| +| `selectCurrentCohort` | `Cohort \| undefined` | +| `selectCurrentCohortId` | `string \| undefined` | +| `selectCurrentCohortName` | `string \| undefined` | +| `selectCurrentCohortFilters` | `FilterSet` | +| `selectCurrentCohortGqlFilters` | filters converted for the API | +| `selectCurrentCohortFiltersByName(state, field)` | `Operation \| undefined` | +| `selectCurrentCohortCaseCount` | `number \| undefined` | +| `selectCurrentCohortModified` | `boolean \| undefined` | +| `selectCurrentCohortSaved` | `boolean \| undefined` | +| `selectCohortNameById(state, id)` | `string \| undefined` | +| `selectCohortFilterSetById(state, id)` | `FilterSet` for any cohort | +| `selectCohortByIdOrName(state, id, name?)` | resolves by id, then `unsavedCohortId`, then name | + +Hooks are available for the common cases: ```typescript -import {useCoreSelector, selectCurrentFilters} from '@gff/core'; +import {useCurrentCohortFilters, useCurrentCohortCounts} from '@gff/core'; -const currentFilters = useSelector(selectCurrentCohortFilters); +const filters = useCurrentCohortFilters(); +const {data: counts, status} = useCurrentCohortCounts(); ``` -By using the selector, the application will be updated when the filters change. The filters are returned as -a `FilterSet` object described above. +Counts are `-1` until the request resolves. You need to check `status` before displaying them. -All the cohorts can be selected using the selector `selectAllCohorts`. This selector returns all the cohorts in the -store. Accessing all the cohorts is done via the selector: +To list cohorts: ```typescript -import {useCoreSelector, selectAllCohorts} from '@gff/core'; +import {useCoreSelector, selectAvailableCohorts} from '@gff/core'; -const allCohorts = useSelector(selectAllCohorts); +const cohorts = useCoreSelector((state) => selectAvailableCohorts(state)); ``` +`selectAvailableCohorts` excludes deleted cohorts. `selectAllCohorts` includes them. ## Using the GDC Data Portal Application API The GDC Data Portal provides a number of hooks for querying the GDC API. These hooks are located in the `@gff/core` package. @@ -472,54 +498,130 @@ Finally, the following hooks are available for querying set size: * `useSsmSetCountsQuery` * `useCaseSetCountsQuery` -## Creating a Cohort +## Cohort Lifecycle + +A cohort is either **unsaved** (held only in the browser) or **saved** (persisted to the GDC +API). A saved cohort with local edits is marked `modified: true` until those edits are +persisted or discarded. + +A user may have **only one unsaved cohort at a time**. `addNewUnsavedCohort` and +`addNewDefaultUnsavedCohort` throw if one already exists. You need to pass `replace: true` to discard it: + +```typescript +import {useCoreDispatch, addNewUnsavedCohort} from '@gff/core'; + +const coreDispatch = useCoreDispatch(); + +coreDispatch(addNewUnsavedCohort({ + filters: {mode: "and", root: {}}, + name: "My Cohort", + replace: true, +})); +``` + +Saving a cohort creates a **new entity** under the id issued by the API, and the unsaved one +is removed. An application holding the previous id should resolve it with +`selectCohortByIdOrName`, which falls back to `unsavedCohortId` and then to the cohort name. + +`discardCohortChanges` reverts a modified cohort to its last saved filters. + +| Selector or constant | Purpose | +|---|---| +| `selectCurrentCohortSaved` | whether the current cohort exists on the server | +| `selectCurrentCohortModified` | whether it has unsaved edits | +| `selectHasUnsavedCohorts` | whether an unsaved cohort already exists | +| `selectUnsavedCohortName` | name of the unsaved cohort, if any | +| `UNSAVED_COHORT_NAME` | default name given to a new unsaved cohort | +| `defaultCohortNameGenerator()` | generates a timestamped default name | -Depending on the application function, it may be beneficial to create a new cohort. Although the GDC Data Portal SDK provides a -number of functions for creating a new cohort, it is highly recommended that the application use the provided `Button` and -`SaveCohortModal` components to create a new cohort. The `Button` and `SaveCohortModal` components are located in -the `@gff/portal-proto` package. -To create a cohort using the SaveCohortModal component the following code can be used: -In summary, the above code flow is: +## Creating a Cohort + +Applications should create cohorts with the `SaveCohortModal` component from +`@gff/portal-components` rather than dispatching cohort actions directly. The modal handles +naming, duplicate-name detection, and saving to the GDC API. -1. The `ProjectsCohortButton` component renders a button with the label "Save New Cohort" -2. When the button is clicked, it sets the state variable `showSaveCohort` to true, which triggers the rendering of - the `SaveCohortModal` component. -3. The `SaveCohortModal` component passed: - * An onClose function that sets the showSaveCohort state variable to false. - * A `filters` prop, which is an object defining the filters for the cohort based on the selected projects. -4. The `SaveCohortModal` will use the passed filter to create, name, and save the cohort when the save button is clicked. +```tsx +import React, {useState} from "react"; +import {Button} from "@mantine/core"; +import {SaveCohortModal} from "@gff/portal-components"; +import {cohortActionsHooks} from "@/features/cohortBuilder/CohortManager/cohortActionHooks"; +import {INVALID_COHORT_NAMES} from "@/features/cohortBuilder/utils"; + +const ProjectsCohortButton = ({pickedProjects}: { pickedProjects: string[] }): JSX.Element => { +    const [showSaveCohort, setShowSaveCohort] = useState(false); + +    return ( +        <> +             + +             setShowSaveCohort(false)} +                filters={{ +                    mode: "and", +                    root: { +                        "cases.project.project_id": { +                            operator: "includes", +                            field: "cases.project.project_id", +                            operands: pickedProjects, +                        }, +                    }, +                }} +                hooks={cohortActionsHooks} +                invalidCohortNames={INVALID_COHORT_NAMES} +            /> +         +    ); +}; +``` -Additional details on the `SaveCohortModal` component can be found in the [Component Library](#component-library) -section. +The modal stays mounted and its visibility is controlled by `opened`. + +| Prop | Required | Description | +|---|---|---| +| `opened` | yes | whether the modal is open or not | +| `onClose` | yes | callback triggered when modal closes | +| `filters` | yes | the filters associated with the cohort | +| `hooks` | yes | collection of hooks for performing saving, deleting, etc operations on cohorts | +| `invalidCohortNames` | yes | list of cohort names that the user is barred from using | +| `initialName` | no | populates initial value of name field | +| `caseFilters` | no | the case filters to use for the cohort | +| `cohortId` | no | id of existing cohort we are saving, if undefined we are not saving a cohort that already exists | +| `createStaticCohort` | no | whether to create a case set from the filters so the cases in the cohort remain static | +| `setAsCurrent` | no | whether to set the new cohort as the user's current cohort, should not also pass in cohortId | +| `saveAs` | no | whether to save existing cohort as new cohort, requires cohortId | ## Altering a Cohort -Altering a cohort is done by dispatching actions to add, remove, or clear filters. The following actions are available -for altering the current cohort: +The following actions modify the current cohort. Each marks it `modified: true` and triggers a +refetch of its counts. * `updateCohortFilter` * `removeCohortFilter` * `clearCohortFilters` -Note that all of these operations are applied to the current cohort. The current cohort is the cohort that is currently -being displayed in the Cohort Management Bar. The current cohort can be programmatically accessed via the `selectCurrentCohort` selector. -The current cohort's filters can be accessed via the `selectCurrentCohortFilters` selector. +They always apply to the current cohort, the one displayed in the Cohort Management Bar, +which can be read with `selectCurrentCohort` and its filters with `selectCurrentCohortFilters`. ### Updating, Removing, and Clearing filters -To update the current selected cohort's filter, the `updateCohortFilter` action can be used. The `updateCohortFilter` -action takes two arguments: +`updateCohortFilter` adds or replaces the filter for a single field: ```typescript interface UpdateFilterParams { - field: string; - operation: Operation; +    field: string; +    operation: Operation; } ``` -where `field` is the field to update and `operation` is the operation to apply to the field. For example to update the -`cases.project.project_id` field to include the project `TCGA-ACC` the following code can be used: +`operation` is a portal-side `Operation`, not a GDC API filter. For example, to filter on the +project `TCGA-ACC`: ```typescript import {useCoreDispatch, updateCohortFilter} from '@gff/core'; @@ -527,42 +629,28 @@ import {useCoreDispatch, updateCohortFilter} from '@gff/core'; const coreDispatch = useCoreDispatch(); coreDispatch(updateCohortFilter({ - field: "cases.project.project_id", - operation: { - op: "in", - content: { - field: "cases.project.project_id", - value: ["TCGA-ACC"], - }, - }, +    field: "cases.project.project_id", +    operation: { +        operator: "includes", +        field: "cases.project.project_id", +        operands: ["TCGA-ACC"], +    }, })); ``` -This will update the current cohort's filter to include the project `TCGA-ACC`. The `removeCohortFilter` action can be -used to remove a filter from the current cohort. The `removeCohortFilter` action takes a single argument: +Because `root` is keyed by field name, this replaces any existing filter on that field. -```typescript -interface RemoveFilterParams { - field: string; -} -``` - -where `field` is the field to remove. For example, to remove the `cases.project.project_id` field from the current -cohort's filter, the following code can be used: +`removeCohortFilter` takes the field name as a string: ```typescript import {useCoreDispatch, removeCohortFilter} from '@gff/core'; const coreDispatch = useCoreDispatch(); -coreDispatch(removeCohortFilter({ - field: "cases.project.project_id", -})); +coreDispatch(removeCohortFilter("cases.project.project_id")); ``` -This will remove the `cases.project.project_id` field from the current cohort's filter. The `clearCohortFilters` action -can be used to clear all the filters from the current cohort. The `clearCohortFilters` action takes no arguments. For -example, to clear all the filters from the current cohort, the following code can be used: +`clearCohortFilters` takes no arguments and resets the cohort to all of the GDC: ```typescript import {useCoreDispatch, clearCohortFilters} from '@gff/core'; @@ -572,59 +660,78 @@ const coreDispatch = useCoreDispatch(); coreDispatch(clearCohortFilters()); ``` -This will clear all the filters from the current cohort. - ## Updating the Cohort Name -The cohort name can be updated using the `updateCohortName` action. The `updateCohortName` action takes a single -argument: - -```typescript -interface UpdateCohortNameParams { - name: string; -} -``` - -where `name` is the new name for the cohort. For example, to update the current cohort's name to `My Cohort`, the -following code can be used: +`updateCohortName` renames the current cohort. It takes the new name as a string: ```typescript import {useCoreDispatch, updateCohortName} from '@gff/core'; const coreDispatch = useCoreDispatch(); -coreDispatch(updateCohortName({ - name: "My Cohort", -})); +coreDispatch(updateCohortName("My Cohort")); ``` -This will update the current cohort's name to `My Cohort`. +This renames the cohort in the store only. For a saved cohort, persist the change with the +`useUpdateFilters` hook from `cohortActionsHooks`, or let `SaveCohortModal` handle it. ## Setting the Current Cohort -The current cohort can be set using the `setCurrentCohort` action. The `setCurrentCohort` action takes a single -argument: +`setActiveCohort` switches the current cohort. It takes the cohort id as a string: ```typescript -interface SetCurrentCohortParams { - cohortId: string; -} +import {useCoreDispatch, setActiveCohort} from '@gff/core'; + +const coreDispatch = useCoreDispatch(); + +coreDispatch(setActiveCohort("1234")); ``` -where `cohortId` is the id of the cohort to set as the current cohort. For example, to set the cohort with id `1234` as -the current cohort, the following code can be used: +The id you pass must belong to a cohort in the store. If it does not, the portal ends up with no +current cohort. Use `selectAvailableCohorts` to get valid ids. + + +## Deleting a Cohort + +`deleteCohortUserAction` marks a cohort as deleted. It does **not** remove it from the store: +the entity is kept, with `removed: true`, so applications still holding its id can continue to +resolve it until the page is reloaded. ```typescript -import {useCoreDispatch, setCurrentCohort} from '@gff/core'; +import {useCoreDispatch, deleteCohortUserAction} from '@gff/core'; const coreDispatch = useCoreDispatch(); -coreDispatch(setCurrentCohort({ - cohortId: "1234", -})); +coreDispatch(deleteCohortUserAction({id: cohortId})); // omit id to delete the current cohort ``` -This will set the cohort with ID `1234` as the current cohort. +Deleted cohorts are excluded from `selectAvailableCohorts` and included in `selectAllCohorts`. + +`removeCohortFromStore` removes the entity outright. It is intended for internal housekeeping, +such as discarding a local cohort after it has been saved or for clean up if a cohort has become outdated, and should not be used to delete a cohort on a user's behalf. + +Neither action deletes the cohort from the GDC API. For a saved cohort, call +`useDeleteCohortMutation` first and dispatch `deleteCohortUserAction` only if the request +succeeds, so that a failed request does not remove the cohort from the interface. + +## Persistence and Session Behaviour + +Cohorts are stored by the GDC API against a context id held in the `gdc_context_id` cookie. No +user account is required. Losing both the cookie and its `localStorage` backup makes previously +saved cohorts unreachable. + +Cohort state is also persisted to `sessionStorage`, which determines what survives each event: + +| Event | Effect on cohorts | +|---|---| +| Page reload | Cohorts are restored, including the unsaved cohort and any unsaved edits. | +| New tab or window | Saved cohorts are refetched from the API; the unsaved cohort is not restored. | +| Log in or log out | Cohort definitions are retained. All derived data is discarded and refetched. | + +Counts, facet values, table results and file lists are derived data: logging in or out changes +what the API returns for identical filters, so the portal discards them at that point. +Applications that maintain their own caches of API results should clear them on the same event. + ## Total Count Information @@ -662,7 +769,7 @@ export type DataStatus = "uninitialized" | "pending" | "fulfilled" | "rejected"; ## Application Card Counts The application cards show the counts for the data required by them. The data types below are supported: -* caseCount +* caseCount * fileCount * genesCount * mutationCount @@ -671,19 +778,19 @@ The application cards show the counts for the data required by them. The data ty * geneExpressionCaseCount * mafFileCount -Each of these use a specific GraphQL query to the GDC Data API to get the count. If an application requires a +Each of these use a specific GraphQL query to the GDC Data API to get the count. If an application requires a specialized count, then the developer will need to implement and register a count function that returns the following: ```typescript [ { data: number, // The count for the specific data type - isFetching: boolean, // True if the query is fetching data + isFetching: boolean, // True if the query is fetching data isSuccess: boolean, // True if query sucessfully completes isError: boolean // True if the query has encountered an error - } + } ] ``` -or use [RTK Query's ```useLazyQuery```](https://redux-toolkit.js.org/rtk-query/api/created-api/hooks#uselazyquery). +or use [RTK Query's ```useLazyQuery```](https://redux-toolkit.js.org/rtk-query/api/created-api/hooks#uselazyquery). For example: ```typescript @@ -748,7 +855,7 @@ import { CountHookRegistry} from "@gff/core"; CountHookRegistry.getInstance().registerHook("ssmCaseCount", useLazySsmsCaseCountQuery); ``` -The count function is now registered with it name passed as the first argument to ```registerHook``` , and is used to set the value of the ```countsField``` in the +The count function is now registered with it name passed as the first argument to ```registerHook``` , and is used to set the value of the ```countsField``` in the application registration described below. An appropriate place to add the registration call is in ```_app.tsx```. @@ -836,7 +943,7 @@ These modals and others, are documented in the Portal 2.0 SDK API documentation. ### Charts -Basic charts are provided for use within an application, although developers are free to use any desired charting +Basic charts are provided for use within an application, although developers are free to use any desired charting library compatible with React 18. The charts provided are: @@ -878,7 +985,7 @@ const BarChart = dynamic(() => import("@/components/charts/BarChart"), { ``` * `Cancer Distribution` - A cancer distribution chart - + ![cancer distribution](images/developers_guide/most-frequently-mutated-genes-bar-chart.png) The `CancerDistribution` component (based on Plotly) is different as it passed the Gene Symbol @@ -972,7 +1079,7 @@ using [lerna](https://lerna.js.org) and [npm](https://www.npmjs.com/), and conta * `@gff/core` - Contains the core components and hooks for the GDC Data Portal. * `@gff/portal-proto` - Contains the UI components and application framework (using NextJS) for the GDC Data Portal. -Note that in the future, the UI components located in the `@gff/portal-proto` package will be refactored into a +Note that in the future, the UI components located in the `@gff/portal-proto` package will be refactored into a separate package , and `@gff/portal-proto` will be renamed to `@gff/portal`. Developers can get started by cloning the repo and following the instructions in @@ -1204,74 +1311,10 @@ export const useProjectsFilters = (): FilterSet => { ## Creating a New Cohort -The Project application allows users to create a new cohort from the selected projects. The cohort is created using the -`SaveCohortModal` component. The `SaveCohortModal` component passes the current cohort filters and the local project -filters to create a new saved cohort. In the case of the Project application, the `SaveCohortModal` component is used -in a button component. The button component is passed the selected projects and the `SaveCohortModal` component is -rendered when the button is clicked. The `SaveCohortModal` component passes the current cohort filters and the local -project filters to create a new saved cohort. The `SaveCohortModal` component is used in the Project application as: - -```tsx -import React, {useState} from "react"; -import {Button, Tooltip} from "@mantine/core"; -import {CountsIcon} from "@/components/tailwindComponents"; -import SaveCohortModal from "@/components/Modals/SaveCohortModal"; - -const ProjectsCohortButton = ({pickedProjects,}: { pickedProjects: string[]; }): JSX.Element => { - const [showSaveCohort, setShowSaveCohort] = useState(false); - - return ( - <> - - - - - - {showSaveCohort && ( - setShowSaveCohort(false)} - filters={{ - mode: "and", - root: { - "cases.project.project_id": { - operator: "includes", - field: "cases.project.project_id", - operands: pickedProjects, - }, - }, - }} - /> - )} - - ); -}; - -export default ProjectsCohortButton; -``` +The Projects applicatio lets users create a cohort from the projects they have selected. `ProjectsCohortButton` renders the button and passes the +selected project ids to `SaveCohortModal` as the new cohort's filters. -This custom button component uses the state variable `showSaveCohort` to determine if the `SaveCohortModal` component -needs to be shown. -The `SaveCohortModal` component is passed to the current list of projects selected by the user and handles the creation of -the cohort and saving it. +See [Creating a Cohort](#creating-a-cohort) for the component's props and a full example. ## Application Demo @@ -1340,14 +1383,14 @@ import ProjectsIcon from "public/user-flow/icons/crowd-of-users.svg"; ... { - name: "Projects", + name: "Projects", icon: (), - tags: [], + tags: [], hasDemo: false, id: "Projects", countsField: "caseCount", From 32797f28ece274e060a4789d7b6ec879e60a798b Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal Date: Mon, 24 Aug 2026 18:39:59 -0400 Subject: [PATCH 02/12] fix typo and add a detail --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index adae9080f..a5a8e004a 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -77,8 +77,7 @@ interface Cohort { } ``` -Deleting a cohort sets `removed: true` rather than removing it, so applications holding its id can still resolve it. -Saving a cohort creates a new entity under the server-issued id; `unsavedCohortId` points back at the local id it replaced. +The most important part of a cohort is the `filters` field: a `FilterSet` object holding the cohort's active filters. When calling the GDC REST or GraphQL API, the `FilterSet` is converted to that API's expected format. ### FilterSet @@ -91,7 +90,6 @@ interface FilterSet { `root` is keyed by field name, so a cohort holds at most one operation per field. An empty `root` means all of the GDC. - ```typescript const allOfGDC: FilterSet = { mode: "and", root: {} }; ``` @@ -1311,7 +1309,7 @@ export const useProjectsFilters = (): FilterSet => { ## Creating a New Cohort -The Projects applicatio lets users create a cohort from the projects they have selected. `ProjectsCohortButton` renders the button and passes the +The Projects application lets users create a cohort from the projects they have selected. `ProjectsCohortButton` renders the button and passes the selected project ids to `SaveCohortModal` as the new cohort's filters. See [Creating a Cohort](#creating-a-cohort) for the component's props and a full example. From b2beb600bc7c1e940ad617d6cc73a5c1c162eabd Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal <101295912+paribartandhakal@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:20:47 -0400 Subject: [PATCH 03/12] Update docs/Data_Portal/Users_Guide/Developers_Guide.md Co-authored-by: wteouchicago <73256434+wteouchicago@users.noreply.github.com> --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index a5a8e004a..cc4d0ac1b 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -77,7 +77,7 @@ interface Cohort { } ``` -The most important part of a cohort is the `filters` field: a `FilterSet` object holding the cohort's active filters. When calling the GDC REST or GraphQL API, the `FilterSet` is converted to that API's expected format. +The most important part of a cohort is the `filters` field: a `FilterSet` object containing the cohort's active filters. When calling the GDC REST or GraphQL API, the `FilterSet` is converted to that API's expected format. ### FilterSet From aa8ead3c8d5406086652413ffe12d3e9592fdd3f Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal <101295912+paribartandhakal@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:21:00 -0400 Subject: [PATCH 04/12] Update docs/Data_Portal/Users_Guide/Developers_Guide.md Co-authored-by: wteouchicago <73256434+wteouchicago@users.noreply.github.com> --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index cc4d0ac1b..956d0ca07 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -88,7 +88,7 @@ interface FilterSet { } ``` -`root` is keyed by field name, so a cohort holds at most one operation per field. An empty `root` means all of the GDC. +`root` is keyed by field name, so a cohort contains at most one operation per field. An empty `root` means all of the GDC. ```typescript const allOfGDC: FilterSet = { mode: "and", root: {} }; From 6987bae06236e9602b169a8ac584c35fb43b3002 Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal <101295912+paribartandhakal@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:21:12 -0400 Subject: [PATCH 05/12] Update docs/Data_Portal/Users_Guide/Developers_Guide.md Co-authored-by: wteouchicago <73256434+wteouchicago@users.noreply.github.com> --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index 956d0ca07..39f6e12b7 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -139,7 +139,7 @@ import { joinFilters } from "@gff/core"; const combined = joinFilters(cohortFilters, localFilters); ``` -`joinFilters` is a shallow merge of `root` and the second argument wins. +`joinFilters` is a shallow merge of `root` and the second argument takes precedence. ### Obtaining Cohort Information From 2607395a13b8af4efcb0a38a9e853b0b481a4cbf Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal <101295912+paribartandhakal@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:21:25 -0400 Subject: [PATCH 06/12] Update docs/Data_Portal/Users_Guide/Developers_Guide.md Co-authored-by: wteouchicago <73256434+wteouchicago@users.noreply.github.com> --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index 39f6e12b7..25a344266 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -498,7 +498,7 @@ Finally, the following hooks are available for querying set size: ## Cohort Lifecycle -A cohort is either **unsaved** (held only in the browser) or **saved** (persisted to the GDC +A cohort is either **unsaved** (stored only in the browser) or **saved** (persisted to the GDC API). A saved cohort with local edits is marked `modified: true` until those edits are persisted or discarded. From 17b2ace9f033ae658fc360c2560bd97d8d4c57e2 Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal <101295912+paribartandhakal@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:21:18 -0400 Subject: [PATCH 07/12] Update docs/Data_Portal/Users_Guide/Developers_Guide.md Co-authored-by: Amy Lehman --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index 25a344266..c55d1812f 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -503,7 +503,7 @@ API). A saved cohort with local edits is marked `modified: true` until those edi persisted or discarded. A user may have **only one unsaved cohort at a time**. `addNewUnsavedCohort` and -`addNewDefaultUnsavedCohort` throw if one already exists. You need to pass `replace: true` to discard it: +`addNewDefaultUnsavedCohort` throw an exception if one already exists. You need to pass `replace: true` to discard it: ```typescript import {useCoreDispatch, addNewUnsavedCohort} from '@gff/core'; From f987be2eb7a7986dbad71b460899ee98ae49c67b Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal <101295912+paribartandhakal@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:21:26 -0400 Subject: [PATCH 08/12] Update docs/Data_Portal/Users_Guide/Developers_Guide.md Co-authored-by: wteouchicago <73256434+wteouchicago@users.noreply.github.com> --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index c55d1812f..72ae8434f 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -716,7 +716,7 @@ succeeds, so that a failed request does not remove the cohort from the interface Cohorts are stored by the GDC API against a context id held in the `gdc_context_id` cookie. No user account is required. Losing both the cookie and its `localStorage` backup makes previously -saved cohorts unreachable. +saved cohorts irretrievable. Cohort state is also persisted to `sessionStorage`, which determines what survives each event: From 718d4536ff4debb82cfb0fcc672dd603dbffc9a0 Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal <101295912+paribartandhakal@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:21:34 -0400 Subject: [PATCH 09/12] Update docs/Data_Portal/Users_Guide/Developers_Guide.md Co-authored-by: wteouchicago <73256434+wteouchicago@users.noreply.github.com> --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index 72ae8434f..aebe16a13 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -714,7 +714,7 @@ succeeds, so that a failed request does not remove the cohort from the interface ## Persistence and Session Behaviour -Cohorts are stored by the GDC API against a context id held in the `gdc_context_id` cookie. No +Cohorts are stored by the GDC API against a context id stored in the `gdc_context_id` cookie. No user account is required. Losing both the cookie and its `localStorage` backup makes previously saved cohorts irretrievable. From a2e01087ca7a52c5c77da6d24279a32138a75c2f Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal <101295912+paribartandhakal@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:21:49 -0400 Subject: [PATCH 10/12] Update docs/Data_Portal/Users_Guide/Developers_Guide.md Co-authored-by: wteouchicago <73256434+wteouchicago@users.noreply.github.com> --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index aebe16a13..01f3108a3 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -605,7 +605,7 @@ refetch of its counts. * `clearCohortFilters` They always apply to the current cohort, the one displayed in the Cohort Management Bar, -which can be read with `selectCurrentCohort` and its filters with `selectCurrentCohortFilters`. +which can be read with `selectCurrentCohort`, and its filters can be read with `selectCurrentCohortFilters`. ### Updating, Removing, and Clearing filters From 3b5c9bb562a45456d867697aa19c9beb3ca4e798 Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal Date: Tue, 25 Aug 2026 15:23:55 -0400 Subject: [PATCH 11/12] replace holding with referencing --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index 01f3108a3..fd62f9c65 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -692,7 +692,7 @@ current cohort. Use `selectAvailableCohorts` to get valid ids. ## Deleting a Cohort `deleteCohortUserAction` marks a cohort as deleted. It does **not** remove it from the store: -the entity is kept, with `removed: true`, so applications still holding its id can continue to +the entity is kept, with `removed: true`, so applications still referencing its id can continue to resolve it until the page is reloaded. ```typescript From 75d8e2195cfb8354780c072f8ca053912ce0ad18 Mon Sep 17 00:00:00 2001 From: Paribartan Dhakal <101295912+paribartandhakal@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:48:11 -0400 Subject: [PATCH 12/12] Update docs/Data_Portal/Users_Guide/Developers_Guide.md Co-authored-by: wteouchicago <73256434+wteouchicago@users.noreply.github.com> --- docs/Data_Portal/Users_Guide/Developers_Guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Data_Portal/Users_Guide/Developers_Guide.md b/docs/Data_Portal/Users_Guide/Developers_Guide.md index fd62f9c65..9d8c42d01 100644 --- a/docs/Data_Portal/Users_Guide/Developers_Guide.md +++ b/docs/Data_Portal/Users_Guide/Developers_Guide.md @@ -518,7 +518,7 @@ coreDispatch(addNewUnsavedCohort({ ``` Saving a cohort creates a **new entity** under the id issued by the API, and the unsaved one -is removed. An application holding the previous id should resolve it with +is removed. An application referencing the previous id should resolve it with `selectCohortByIdOrName`, which falls back to `unsavedCohortId` and then to the cohort name. `discardCohortChanges` reverts a modified cohort to its last saved filters.