Skip to content

Commit

Permalink
Austenem/CAT-754 Refactor "My Lists" (#3671)
Browse files Browse the repository at this point in the history
* add ukv endpoint

* pull authenticated lists into my lists page

* implement add functionality

* refactor hooks

* update deletion functionality

* update lists functionality

* fix typing

* add entity to list functionality

* edit and deletion functionality

* liststobedeleted functionality

* remove savedlistscontent

* update stores

* move logic to hooks

* typescript conversions and list page update

* adjust useeffect and language

* add documentation, minor updates

* fix re-rendering issue

* copy over local lists and entities

* update local to remote copy logic

* update save entity logic

* update alerts

* continue adding alerts

* add save entities button to search page

* update saved items table, add button to table

* adjust button tooltips

* add barrel files

* convert to ts

* continue conversions

* separate out api and store

* update tests

* fix minor uuid bug, adjust menu button

* sort items

* continue to add sorting

* clean up and add changelog

* update list page

* update isLoading bool to pass test

* remove lists page for unauthenticated users

* refactor alerts

* hide list buttons from unauthenticated users

* finish refactoring hooks

* fix new list dialog

* minor updates and extend alerts

* update jest test

* update test and implement copy logic

* update hook to use default export

* update storage description

* minor cleanup

* finish deleting stores

* update logged out description

* add save entity button to processed datasets

* review cleanup 1

* update import

* update hooks

* Austenem/CAT-1135 Update /services (#3687)

* add ukv service

* remove logs

* add changelog

* memoize entity counts

* Austenem/CAT-1113 Add list metrics (#3689)

* add categories and update organ page table

* finish adding tracking

* finish adding tested tracking

* consolidate save entity tracking

* update imports

* add changelog

* add ref

* add additional check

* separate out save entity hook
  • Loading branch information
austenem authored Feb 13, 2025
1 parent 68d215f commit 08ca57a
Show file tree
Hide file tree
Showing 81 changed files with 1,504 additions and 731 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-add-my-lists-metrics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add metrics for the "My Lists" feature.
2 changes: 2 additions & 0 deletions CHANGELOG-refactor-my-lists.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Update "My Lists" feature to persist across devices for logged-in users.
- Update "My Lists" UI and messaging.
1 change: 1 addition & 0 deletions CHANGELOG-update-services.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Update services page to include new UKV endpoint.
1 change: 1 addition & 0 deletions context/app/default_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class DefaultConfig(object):
USER_TEMPLATES_ENDPOINT = 'should-be-overriden'
UBKG_ENDPOINT = 'should-be-overridden'
SOFT_ASSAY_ENDPOINT = 'should-be-overridden'
UKV_ENDPOINT = 'should-be-overridden'

SECRET_KEY = 'should-be-overridden'
APP_CLIENT_ID = 'should-be-overridden'
Expand Down
1 change: 1 addition & 0 deletions context/app/static/js/components/Contexts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface AppContextType {
workspacesEndpoint: string;
userTemplatesEndpoint: string;
ubkgEndpoint: string;
ukvEndpoint: string;
protocolsClientToken: string;
isAuthenticated: boolean;
isWorkspacesUser: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ function ServiceStatusTable({
workspacesEndpoint,
ubkgEndpoint,
softAssayEndpoint,
ukvEndpoint,
}) {
const gatewayStatus = useGatewayStatus(`${gatewayEndpoint}/status.json`);

Expand Down Expand Up @@ -105,6 +106,12 @@ function ServiceStatusTable({
endpointUrl: ubkgEndpoint,
githubUrl: 'https://github.com/x-atlas-consortia/hs-ontology-api',
}),
buildServiceStatus({
apiName: 'ukv-api',
response: gatewayStatus.ukv_api,
endpointUrl: ukvEndpoint,
githubUrl: 'https://github.com/x-atlas-consortia/user-key-value-api',
}),
]
: [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import React from 'react';
import PropTypes from 'prop-types';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';

import type { Entity } from 'js/components/types';
import { CollapsibleDetailPageSection } from 'js/components/detailPage/DetailPageSection';
import RelatedEntitiesTable from 'js/components/detailPage/related-entities/RelatedEntitiesTable';
import BulkDownloadButton from 'js/components/bulkDownload/buttons/BulkDownloadButton';
import SaveEntitiesButton from 'js/components/savedLists/SaveEntitiesButton';
import { SpacedSectionButtonRow } from 'js/shared-styles/sections/SectionButtonRow';
import { sectionIconMap } from 'js/shared-styles/icons/sectionIconMap';

Expand All @@ -33,7 +35,12 @@ function CollectionDatasetsTable({ datasets }: CollectionDatasetsTableProps) {
{datasets.length} Datasets
</Typography>
}
buttons={<BulkDownloadButton uuids={uuids} tooltip="Bulk download files for datasets in this table." />}
buttons={
<Stack direction="row" spacing={1}>
<SaveEntitiesButton uuids={uuids} entity_type="Dataset" />
<BulkDownloadButton uuids={uuids} tooltip="Bulk download files for datasets in this table." />
</Stack>
}
/>
<Paper>
<RelatedEntitiesTable columns={columns} entities={data} entityType="dataset" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@ import React, { PropsWithChildren } from 'react';
import { createPortal } from 'react-dom';
import Stack from '@mui/material/Stack';

import useEntityStore, { savedAlertStatus, editedAlertStatus, EntityStore } from 'js/stores/useEntityStore';
import { leftRouteBoundaryID, rightRouteBoundaryID } from 'js/components/Routes/Route/Route';
import { SavedListsSuccessAlert } from 'js/components/savedLists/SavedListsAlerts';
import TableOfContents from 'js/shared-styles/sections/TableOfContents';
import { TableOfContentsItems } from 'js/shared-styles/sections/TableOfContents/types';
import { leftRouteBoundaryID, rightRouteBoundaryID } from 'js/components/Routes/Route/Route';
import { SectionOrder, getSections } from 'js/shared-styles/sections/TableOfContents/utils';
import { StyledAlert } from './style';

const entityStoreSelector = (state: EntityStore) => ({
shouldDisplaySavedOrEditedAlert: state.shouldDisplaySavedOrEditedAlert,
setShouldDisplaySavedOrEditedAlert: state.setShouldDisplaySavedOrEditedAlert,
});

interface DetailLayoutProps extends PropsWithChildren {
sections: SectionOrder;
Expand Down Expand Up @@ -46,34 +40,12 @@ export function HelperPanelPortal({ children }: PropsWithChildren) {
);
}

function DetailAlert() {
const { shouldDisplaySavedOrEditedAlert, setShouldDisplaySavedOrEditedAlert } = useEntityStore(entityStoreSelector);

if (shouldDisplaySavedOrEditedAlert === savedAlertStatus) {
return (
<StyledAlert severity="success" onClose={() => setShouldDisplaySavedOrEditedAlert(false)}>
Successfully added to My Saves List. All lists are currently stored on local storage and are not transferable
between devices.
</StyledAlert>
);
}

if (shouldDisplaySavedOrEditedAlert === editedAlertStatus) {
return (
<StyledAlert severity="success" onClose={() => setShouldDisplaySavedOrEditedAlert(false)}>
Successfully updated save status. All lists are currently stored on local storage and are not transferable
between devices.
</StyledAlert>
);
}
}

function DetailLayout({ sections, children, isLoading = false }: DetailLayoutProps) {
const items = getSections(sections);

return (
<>
<DetailAlert />
<SavedListsSuccessAlert />
<TableOfContentsPortal items={items} isLoading={isLoading} />
{children}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SecondaryBackgroundTooltip } from 'js/shared-styles/tooltips';
import IconButton from '@mui/material/IconButton';
import ContentCopyIcon from '@mui/icons-material/ContentCopyRounded';
import Stack from '@mui/material/Stack';
import SummarySaveEntityButton from 'js/components/detailPage/summary/SummarySaveEntityButton/SummarySaveEntityButton';
import { useVitessceConfLink } from 'js/pages/Dataset/hooks';
import StatusIcon from '../../StatusIcon';
import { useProcessedDatasetContext } from './ProcessedDatasetContext';
Expand Down Expand Up @@ -41,6 +42,7 @@ export function DatasetTitle() {
</SecondaryBackgroundTooltip>
<Stack ml="auto" direction="row" gap={1} alignItems="center">
{conf && <VisualizationIconButton href={vitessceConfUrl} />}
<SummarySaveEntityButton uuid={uuid} />
<SummaryJSONButton entity_type={entity_type} uuid={uuid} />
<VersionSelect />
</Stack>
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import UnfoldLessRoundedIcon from '@mui/icons-material/UnfoldLessRounded';

import { TooltipButtonProps, TooltipIconButton } from 'js/shared-styles/buttons/TooltipButton';
import { CheckIcon, EditSavedEntityIcon, FileIcon, SaveEntityIcon } from 'js/shared-styles/icons';
import useEntityStore, { savedAlertStatus, SummaryViewsType } from 'js/stores/useEntityStore';
import { SummaryViewsType } from 'js/stores/useEntityStore';
import { useTrackEntityPageEvent } from 'js/components/detailPage/useTrackEntityPageEvent';
import EditSavedStatusDialog from 'js/components/savedLists/EditSavedStatusDialog';
import useSavedEntitiesStore, { SavedEntitiesStore } from 'js/stores/useSavedEntitiesStore';
import { Entity } from 'js/components/types';
import { AllEntityTypes } from 'js/shared-styles/icons/entityIconMap';
import { useFlaskDataContext } from 'js/components/Contexts';
import { useAppContext, useFlaskDataContext } from 'js/components/Contexts';
import { sectionIconMap } from 'js/shared-styles/icons/sectionIconMap';
import { useIsLargeDesktop } from 'js/hooks/media-queries';
import ProcessedDataWorkspaceMenu from 'js/components/detailPage/ProcessedData/ProcessedDataWorkspaceMenu';
import useSavedLists from 'js/components/savedLists/hooks';
import WorkspacesIcon from 'assets/svg/workspaces.svg';

function ActionButton<E extends ElementType = IconButtonTypeMap['defaultComponent']>({
Expand Down Expand Up @@ -44,25 +44,13 @@ function JSONButton({ entity_type, uuid }: Pick<Entity, 'uuid'> & { entity_type:
}

function SaveEntityButton({ uuid }: Pick<Entity, 'uuid'>) {
const saveEntity = useSavedEntitiesStore((state) => state.saveEntity);
const setShouldDisplaySavedOrEditedAlert = useEntityStore((state) => state.setShouldDisplaySavedOrEditedAlert);
const { useHandleSaveEntity } = useSavedLists();
const handleSaveEntity = useHandleSaveEntity({ entityUUID: uuid });

const trackSave = useTrackEntityPageEvent();

return (
<ActionButton
onClick={() => {
saveEntity(uuid);
trackSave({ action: 'Save To List', label: uuid });
setShouldDisplaySavedOrEditedAlert(savedAlertStatus);
}}
icon={SaveEntityIcon}
tooltip="Save to list"
/>
);
return <ActionButton onClick={handleSaveEntity} icon={SaveEntityIcon} tooltip="Save to list" />;
}

function EditSavedEntityButton({ entity_type, uuid }: Pick<Entity, 'uuid'> & { entity_type: AllEntityTypes }) {
function EditSavedEntityButton({ uuid }: Pick<Entity, 'uuid'>) {
const [dialogIsOpen, setDialogIsOpen] = useState(false);

return (
Expand All @@ -74,12 +62,7 @@ function EditSavedEntityButton({ entity_type, uuid }: Pick<Entity, 'uuid'> & { e
icon={EditSavedEntityIcon}
tooltip="Edit saved status"
/>
<EditSavedStatusDialog
dialogIsOpen={dialogIsOpen}
setDialogIsOpen={setDialogIsOpen}
uuid={uuid}
entity_type={entity_type}
/>
<EditSavedStatusDialog dialogIsOpen={dialogIsOpen} setDialogIsOpen={setDialogIsOpen} uuid={uuid} />
</>
);
}
Expand All @@ -88,16 +71,15 @@ function WorkspaceSVGIcon({ color = 'primary', ...props }: SvgIconProps) {
return <SvgIcon component={WorkspacesIcon} color={color} {...props} />;
}

const useSavedEntitiesSelector = (state: SavedEntitiesStore) => state.savedEntities;
function SaveEditEntityButton({ uuid }: Pick<Entity, 'uuid'>) {
const { savedEntities } = useSavedLists();
const { isAuthenticated } = useAppContext();

function SaveEditEntityButton({ entity_type, uuid }: Pick<Entity, 'uuid'> & { entity_type: AllEntityTypes }) {
const savedEntities = useSavedEntitiesStore(useSavedEntitiesSelector);
if (!isAuthenticated) {
return null;
}

return uuid in savedEntities ? (
<EditSavedEntityButton uuid={uuid} entity_type={entity_type} />
) : (
<SaveEntityButton uuid={uuid} />
);
return uuid in savedEntities.savedEntities ? <EditSavedEntityButton uuid={uuid} /> : <SaveEntityButton uuid={uuid} />;
}

function ViewSelectChip({
Expand Down Expand Up @@ -197,7 +179,7 @@ function EntityHeaderActionButtons({
return (
<Stack direction="row" spacing={1} alignItems="center">
{isLargeDesktop && <ViewSelectChips selectedView={view} setView={setView} entity_type={entity_type} />}
<SaveEditEntityButton uuid={uuid} entity_type={entity_type} />
<SaveEditEntityButton uuid={uuid} />
<JSONButton entity_type={entity_type} uuid={uuid} />
{isDataset && (
<ProcessedDataWorkspaceMenu
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Stack from '@mui/material/Stack';
import { useFlaskDataContext } from 'js/components/Contexts';
import { useTrackEntityPageEvent } from 'js/components/detailPage/useTrackEntityPageEvent';
import BulkDownloadButton from 'js/components/bulkDownload/buttons/BulkDownloadButton';
import SaveEntitiesButton from 'js/components/savedLists/SaveEntitiesButton';

interface RelatedEntitiesSectionHeaderProps {
searchPageHref: string;
Expand All @@ -19,6 +20,7 @@ export function RelatedEntitiesSectionActions({ searchPageHref, uuids }: Related

return (
<Stack direction="row" spacing={1}>
<SaveEntitiesButton uuids={uuids} entity_type="Dataset" />
<BulkDownloadButton uuids={uuids} tooltip="Bulk download files for datasets in this table." />
<Button
variant="contained"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import LabelledSectionText from 'js/shared-styles/sections/LabelledSectionText';
import OutboundIconLink from 'js/shared-styles/Links/iconLinks/OutboundIconLink';
import Citation from 'js/components/detailPage/Citation';
import { isCollection, isDataset, isPublication } from 'js/components/types';
import { SavedEntitiesList } from 'js/components/savedLists/types';
import { useFlaskDataContext } from 'js/components/Contexts';
import { getCollectionDOI } from 'js/pages/Collection/utils';
import { getEntityCreationInfo } from 'js/helpers/functions';
Expand Down Expand Up @@ -117,11 +118,25 @@ function CollectionCitation() {
function SummaryBodyContent({
isEntityHeader = false,
direction = 'column',
description: propDescription,
creationLabel: propCreationLabel,
creationDate: propCreationDate,
...stackProps
}: { isEntityHeader?: boolean } & Partial<StackProps>) {
}: {
isEntityHeader?: boolean;
description?: string;
creationLabel?: string;
creationDate?: string;
} & Partial<StackProps> &
Partial<SavedEntitiesList>) {
const { entity } = useFlaskDataContext();
const { description } = entity;
const { creationLabel, creationDate } = getEntityCreationInfo(entity);

// Use description and creation info from props if provided, otherwise use entity data
const description = propDescription ?? entity.description;
const { creationLabel, creationDate } =
propCreationLabel && propCreationDate
? { creationLabel: propCreationLabel, creationDate: propCreationDate }
: getEntityCreationInfo(entity);

if (isPublication(entity)) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { useState } from 'react';
import { useAppContext } from 'js/components/Contexts';
import useSavedLists from 'js/components/savedLists/hooks';
import { Entity } from 'js/components/types';
import EditSavedStatusDialog from 'js/components/savedLists/EditSavedStatusDialog';
import { WhiteRectangularTooltipIconButton } from 'js/shared-styles/buttons/TooltipButton';
import { EditSavedEntityIcon, SaveEntityIcon } from 'js/shared-styles/icons';

function SaveEntityButton({ uuid }: Pick<Entity, 'uuid'>) {
const { useHandleSaveEntity } = useSavedLists();
const handleSaveEntity = useHandleSaveEntity({ entityUUID: uuid });

return (
<WhiteRectangularTooltipIconButton onClick={handleSaveEntity} tooltip="Save to list">
<SaveEntityIcon color="primary" />
</WhiteRectangularTooltipIconButton>
);
}

function EditSavedEntityButton({ uuid }: Pick<Entity, 'uuid'>) {
const [dialogIsOpen, setDialogIsOpen] = useState(false);

return (
<>
<WhiteRectangularTooltipIconButton
onClick={() => {
setDialogIsOpen(true);
}}
tooltip="Edit saved status"
>
<EditSavedEntityIcon color="primary" />
</WhiteRectangularTooltipIconButton>
<EditSavedStatusDialog dialogIsOpen={dialogIsOpen} setDialogIsOpen={setDialogIsOpen} uuid={uuid} />
</>
);
}

export default function SummarySaveEntityButton({ uuid }: Pick<Entity, 'uuid'>) {
const { savedEntities } = useSavedLists();
const { isAuthenticated } = useAppContext();

if (!isAuthenticated) {
return null;
}

return uuid in savedEntities.savedEntities ? <EditSavedEntityButton uuid={uuid} /> : <SaveEntityButton uuid={uuid} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import SummarySaveEntityButton from './SummarySaveEntityButton';

export default SummarySaveEntityButton;
Loading

0 comments on commit 08ca57a

Please sign in to comment.