@databiosphere/findable-ui 35.1.0 → 35.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/lib/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestDownload/fileManifestDownload.js +21 -29
  4. package/lib/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestSpreadsheet/fileManifestSpreadsheet.js +21 -36
  5. package/lib/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/constants.d.ts +2 -0
  6. package/lib/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/constants.js +22 -0
  7. package/lib/components/Table/components/TableCell/components/CodeCell/codeCell.d.ts +3 -0
  8. package/lib/components/Table/components/TableCell/components/CodeCell/codeCell.js +9 -0
  9. package/lib/components/Table/components/TableCell/components/CodeCell/codeCell.styles.d.ts +5 -0
  10. package/lib/components/Table/components/TableCell/components/CodeCell/codeCell.styles.js +15 -0
  11. package/lib/hooks/useFileManifest/useFileManifestDownload.d.ts +2 -2
  12. package/lib/hooks/useFileManifest/useFileManifestDownload.js +3 -10
  13. package/lib/hooks/useFileManifest/useFileManifestSpreadsheet.d.ts +5 -4
  14. package/lib/hooks/useFileManifest/useFileManifestSpreadsheet.js +24 -20
  15. package/package.json +1 -1
  16. package/src/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestDownload/fileManifestDownload.tsx +43 -71
  17. package/src/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/components/FileManifestSpreadsheet/fileManifestSpreadsheet.tsx +52 -74
  18. package/src/components/Export/components/ManifestDownload/components/ManifestDownloadEntity/constants.ts +24 -0
  19. package/src/components/Table/components/TableCell/components/CodeCell/codeCell.styles.ts +16 -0
  20. package/src/components/Table/components/TableCell/components/CodeCell/codeCell.tsx +21 -0
  21. package/src/hooks/useFileManifest/useFileManifestDownload.ts +9 -16
  22. package/src/hooks/useFileManifest/useFileManifestSpreadsheet.ts +41 -26
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "35.1.0"
2
+ ".": "35.2.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [35.2.0](https://github.com/DataBiosphere/findable-ui/compare/v35.1.0...v35.2.0) (2025-06-16)
4
+
5
+
6
+ ### Features
7
+
8
+ * add table <code> element ([#525](https://github.com/DataBiosphere/findable-ui/issues/525)) ([#527](https://github.com/DataBiosphere/findable-ui/issues/527)) ([02cb3b6](https://github.com/DataBiosphere/findable-ui/commit/02cb3b698b25a4295fc5ea2048541869301b918f))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * make user request project metadata by clicking the request link button ([#530](https://github.com/DataBiosphere/findable-ui/issues/530)) ([#531](https://github.com/DataBiosphere/findable-ui/issues/531)) ([b645e0f](https://github.com/DataBiosphere/findable-ui/commit/b645e0fe5604dfc7fc195f91747d4a7985b39e4d))
14
+
3
15
  ## [35.1.0](https://github.com/DataBiosphere/findable-ui/compare/v35.0.3...v35.1.0) (2025-06-05)
4
16
 
5
17
 
@@ -1,49 +1,41 @@
1
- import { ButtonBase, TableBody, TableCell, TableRow, Tooltip, } from "@mui/material";
1
+ import { Button, TableBody, TableCell, TableRow, Tooltip } from "@mui/material";
2
2
  import copy from "copy-to-clipboard";
3
- import React, { useRef } from "react";
3
+ import React from "react";
4
4
  import { useDownloadStatus } from "../../../../../../../../hooks/useDownloadStatus";
5
5
  import { useFileManifestDownload } from "../../../../../../../../hooks/useFileManifest/useFileManifestDownload";
6
6
  import { useLoginGuard } from "../../../../../../../../providers/loginGuard/hook";
7
+ import { BUTTON_PROPS } from "../../../../../../../common/Button/constants";
7
8
  import { ButtonGroup } from "../../../../../../../common/ButtonGroup/buttonGroup";
8
- import { ButtonGroupButton } from "../../../../../../../common/ButtonGroup/components/ButtonGroupButton/buttonGroupButton";
9
9
  import { ContentCopyIconSmall, DownloadIconSmall, } from "../../../../../../../common/CustomIcon/common/constants";
10
10
  import { FluidPaper, GridPaper, } from "../../../../../../../common/Paper/paper.styles";
11
11
  import { Loading, LOADING_PANEL_STYLE, } from "../../../../../../../Loading/loading";
12
12
  import { GridTable } from "../../../../../../../Table/common/gridTable.styles";
13
+ import { TOOLTIP_PROPS } from "../../constants";
13
14
  import { SectionTitle, TableContainer, } from "../../manifestDownloadEntity.styles";
14
15
  export const FileManifestDownload = ({ filters, }) => {
15
- const downloadRef = useRef(null);
16
16
  const { disabled, message } = useDownloadStatus();
17
- const { fileName, isIdle, isLoading, manifestURL } = useFileManifestDownload(filters, disabled);
18
- const isInProgress = (isIdle || isLoading) && !disabled;
19
- const isReady = Boolean(manifestURL) || disabled;
17
+ const { fileName, isIdle, isLoading, manifestURL, requestManifest } = useFileManifestDownload(filters);
20
18
  // Prompt user for login before download and copy, if required.
21
19
  const { requireLogin } = useLoginGuard();
22
- // Copies file manifest.
23
- const copyManifestURL = (url) => {
24
- if (!url)
25
- return;
26
- copy(url);
27
- };
28
- // Downloads file manifest.
29
- const downloadManifestURL = () => {
30
- downloadRef.current?.click();
31
- };
32
20
  return (React.createElement(FluidPaper, null,
33
21
  React.createElement(GridPaper, null,
34
22
  React.createElement(SectionTitle, null, "File Manifest"),
35
23
  React.createElement(TableContainer, null,
36
- React.createElement(Loading, { loading: isInProgress, panelStyle: LOADING_PANEL_STYLE.INHERIT }),
37
- React.createElement(GridTable, { gridTemplateColumns: isReady ? "auto 1fr" : "1fr" },
24
+ React.createElement(Loading, { loading: isLoading, panelStyle: LOADING_PANEL_STYLE.INHERIT }),
25
+ React.createElement(GridTable, { gridTemplateColumns: "auto 1fr" },
38
26
  React.createElement(TableBody, null,
39
- React.createElement(TableRow, null, isInProgress ? (React.createElement(TableCell, null)) : isReady ? (React.createElement(React.Fragment, null,
40
- React.createElement(TableCell, null,
41
- React.createElement(ButtonBase, { disabled: disabled, href: manifestURL ?? "", ref: downloadRef, sx: { display: "none" } }),
42
- React.createElement(Tooltip, { arrow: true, title: message },
43
- React.createElement("span", null,
44
- React.createElement(ButtonGroup, { Buttons: [
45
- React.createElement(ButtonGroupButton, { key: "download", action: "Download file manifest", disabled: disabled, label: React.createElement(DownloadIconSmall, null), onClick: () => requireLogin(downloadManifestURL) }),
46
- React.createElement(ButtonGroupButton, { key: "copy", action: "Copy file manifest", disabled: disabled, label: React.createElement(ContentCopyIconSmall, null), onClick: () => requireLogin(() => copyManifestURL(manifestURL)) }),
47
- ] })))),
48
- React.createElement(TableCell, null, fileName))) : (React.createElement(TableCell, null, "The manifest is not available for this project.")))))))));
27
+ React.createElement(TableRow, null, isIdle || isLoading ? (React.createElement(TableCell, null,
28
+ React.createElement(Tooltip, { ...TOOLTIP_PROPS, title: message },
29
+ React.createElement("span", null,
30
+ React.createElement(Button, { ...BUTTON_PROPS.PRIMARY_CONTAINED, disabled: disabled || isLoading, onClick: () => requireLogin(requestManifest) }, "Request link"))))) : (React.createElement(React.Fragment, null,
31
+ manifestURL && (React.createElement(TableCell, null,
32
+ React.createElement(ButtonGroup, { Buttons: [
33
+ React.createElement(Button, { key: "download", download: true, href: manifestURL },
34
+ React.createElement(DownloadIconSmall, null)),
35
+ React.createElement(Button, { key: "copy", onClick: () => copy(manifestURL) },
36
+ React.createElement(ContentCopyIconSmall, null)),
37
+ ] }))),
38
+ React.createElement(TableCell, null, manifestURL
39
+ ? fileName
40
+ : "The manifest is not available for this project."))))))))));
49
41
  };
@@ -1,53 +1,38 @@
1
- import { ButtonBase, TableBody, TableCell, TableRow, Tooltip, } from "@mui/material";
1
+ import { Button, TableBody, TableCell, TableRow, Tooltip } from "@mui/material";
2
2
  import copy from "copy-to-clipboard";
3
- import React, { useEffect, useRef } from "react";
3
+ import React from "react";
4
4
  import { useDownloadStatus } from "../../../../../../../../hooks/useDownloadStatus";
5
5
  import { useFileManifestSpreadsheet } from "../../../../../../../../hooks/useFileManifest/useFileManifestSpreadsheet";
6
- import { useRequestFileLocation } from "../../../../../../../../hooks/useRequestFileLocation";
6
+ import { BUTTON_PROPS } from "../../../../../../../common/Button/constants";
7
7
  import { ButtonGroup } from "../../../../../../../common/ButtonGroup/buttonGroup";
8
- import { ButtonGroupButton } from "../../../../../../../common/ButtonGroup/components/ButtonGroupButton/buttonGroupButton";
9
8
  import { ContentCopyIconSmall, DownloadIconSmall, } from "../../../../../../../common/CustomIcon/common/constants";
10
9
  import { FluidPaper, GridPaper, } from "../../../../../../../common/Paper/paper.styles";
11
10
  import { Loading, LOADING_PANEL_STYLE, } from "../../../../../../../Loading/loading";
12
11
  import { GridTable } from "../../../../../../../Table/common/gridTable.styles";
12
+ import { TOOLTIP_PROPS } from "../../constants";
13
13
  import { SectionTitle, TableContainer, } from "../../manifestDownloadEntity.styles";
14
14
  export const FileManifestSpreadsheet = ({ filters, }) => {
15
- const downloadRef = useRef(null);
16
15
  const { disabled, message } = useDownloadStatus();
17
- const { exists, fileName, fileUrl } = useFileManifestSpreadsheet(filters, disabled) || {};
18
- const { data, isLoading, run } = useRequestFileLocation(fileUrl);
19
- const spreadsheetURL = data?.location;
20
- const isInProgress = (exists === undefined || isLoading) && !disabled;
21
- const isReady = Boolean(spreadsheetURL) || disabled;
22
- // Copies metadata spreadsheet.
23
- const copyMetadataURL = (url) => {
24
- if (!url)
25
- return;
26
- copy(url);
27
- };
28
- // Downloads metadata spreadsheet.
29
- const downloadMetadataURL = () => {
30
- downloadRef.current?.click();
31
- };
32
- // Requests metadata spreadsheet.
33
- useEffect(() => {
34
- run();
35
- }, [fileUrl, run]);
16
+ const { fileName, isIdle = false, isLoading = false, requestManifest, spreadsheetUrl, } = useFileManifestSpreadsheet(filters) || {};
36
17
  return (React.createElement(FluidPaper, null,
37
18
  React.createElement(GridPaper, null,
38
19
  React.createElement(SectionTitle, null, "Metadata"),
39
20
  React.createElement(TableContainer, null,
40
- React.createElement(Loading, { loading: isInProgress, panelStyle: LOADING_PANEL_STYLE.INHERIT }),
41
- React.createElement(GridTable, { gridTemplateColumns: isReady ? "auto 1fr" : "1fr" },
21
+ React.createElement(Loading, { loading: isLoading, panelStyle: LOADING_PANEL_STYLE.INHERIT }),
22
+ React.createElement(GridTable, { gridTemplateColumns: "auto 1fr" },
42
23
  React.createElement(TableBody, null,
43
- React.createElement(TableRow, null, isInProgress ? (React.createElement(TableCell, null)) : isReady ? (React.createElement(React.Fragment, null,
44
- React.createElement(TableCell, null,
45
- React.createElement(ButtonBase, { disabled: disabled, download: true, href: spreadsheetURL ?? "", ref: downloadRef, sx: { display: "none" } }),
46
- React.createElement(Tooltip, { arrow: true, title: message },
47
- React.createElement("span", null,
48
- React.createElement(ButtonGroup, { Buttons: [
49
- React.createElement(ButtonGroupButton, { key: "download", action: "Download metadata spreadsheet", disabled: disabled, label: React.createElement(DownloadIconSmall, null), onClick: downloadMetadataURL }),
50
- React.createElement(ButtonGroupButton, { key: "copy", action: "Copy metadata spreadsheet", disabled: disabled, label: React.createElement(ContentCopyIconSmall, null), onClick: () => copyMetadataURL(spreadsheetURL) }),
51
- ] })))),
52
- React.createElement(TableCell, null, fileName))) : (React.createElement(TableCell, null, "The metadata is not available for this project.")))))))));
24
+ React.createElement(TableRow, null, isIdle || isLoading ? (React.createElement(TableCell, null,
25
+ React.createElement(Tooltip, { ...TOOLTIP_PROPS, title: message },
26
+ React.createElement("span", null,
27
+ React.createElement(Button, { ...BUTTON_PROPS.PRIMARY_CONTAINED, disabled: disabled || isLoading, onClick: () => requestManifest?.() }, "Request link"))))) : (React.createElement(React.Fragment, null,
28
+ spreadsheetUrl && (React.createElement(TableCell, null,
29
+ React.createElement(ButtonGroup, { Buttons: [
30
+ React.createElement(Button, { key: "download", download: true, href: spreadsheetUrl },
31
+ React.createElement(DownloadIconSmall, null)),
32
+ React.createElement(Button, { key: "copy", onClick: () => copy(spreadsheetUrl) },
33
+ React.createElement(ContentCopyIconSmall, null)),
34
+ ] }))),
35
+ React.createElement(TableCell, null, spreadsheetUrl
36
+ ? fileName
37
+ : "The metadata is not available for this project."))))))))));
53
38
  };
@@ -0,0 +1,2 @@
1
+ import { TooltipProps } from "@mui/material";
2
+ export declare const TOOLTIP_PROPS: Partial<TooltipProps>;
@@ -0,0 +1,22 @@
1
+ export const TOOLTIP_PROPS = {
2
+ arrow: true,
3
+ slotProps: {
4
+ popper: {
5
+ modifiers: [
6
+ {
7
+ name: "offset",
8
+ options: {
9
+ offset: [0, -4],
10
+ },
11
+ },
12
+ {
13
+ name: "preventOverflow",
14
+ options: { padding: 8 },
15
+ },
16
+ ],
17
+ },
18
+ tooltip: {
19
+ sx: { maxWidth: "none" },
20
+ },
21
+ },
22
+ };
@@ -0,0 +1,3 @@
1
+ import { CellContext, RowData } from "@tanstack/react-table";
2
+ import { BaseComponentProps } from "../../../../../types";
3
+ export declare const CodeCell: <T extends RowData, TValue extends string = string>({ className, getValue, }: BaseComponentProps & CellContext<T, TValue>) => JSX.Element | null;
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { CHIP_PROPS } from "../../../../../../styles/common/mui/chip";
3
+ import { StyledChip } from "./codeCell.styles";
4
+ export const CodeCell = ({ className, getValue, }) => {
5
+ const value = getValue();
6
+ if (!value)
7
+ return null;
8
+ return (React.createElement(StyledChip, { className: className, color: CHIP_PROPS.COLOR.DEFAULT, label: value, size: CHIP_PROPS.SIZE.SMALL }));
9
+ };
@@ -0,0 +1,5 @@
1
+ export declare const StyledChip: import("@emotion/styled").StyledComponent<import("@mui/material").ChipOwnProps & import("@mui/material/OverridableComponent").CommonProps & Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
2
+ ref?: ((instance: HTMLDivElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLDivElement> | null | undefined;
3
+ }, "label" | "style" | "className" | "classes" | "tabIndex" | "color" | "children" | "sx" | "variant" | "disabled" | "size" | "avatar" | "clickable" | "deleteIcon" | "icon" | "onDelete" | "skipFocusWhenDisabled"> & {
4
+ theme?: import("@emotion/react").Theme;
5
+ }, {}, {}>;
@@ -0,0 +1,15 @@
1
+ import styled from "@emotion/styled";
2
+ import { Chip } from "@mui/material";
3
+ import { PALETTE } from "../../../../../../styles/common/constants/palette";
4
+ export const StyledChip = styled(Chip) `
5
+ border-radius: 4px;
6
+ box-shadow: 0 0 0 2px ${PALETTE.COMMON_WHITE};
7
+ height: auto;
8
+ justify-self: flex-start;
9
+ min-width: 0;
10
+
11
+ .MuiChip-label {
12
+ padding: 2px 8px;
13
+ white-space: normal;
14
+ }
15
+ `;
@@ -4,11 +4,11 @@ export interface ManifestDownload {
4
4
  isIdle: boolean;
5
5
  isLoading: boolean;
6
6
  manifestURL?: string;
7
+ requestManifest: () => void;
7
8
  }
8
9
  /**
9
10
  * Returns file manifest download url and file name.
10
11
  * @param filters - Filters.
11
- * @param disabled - Disabled.
12
12
  * @returns file manifest download url and file name.
13
13
  */
14
- export declare const useFileManifestDownload: (filters: Filters, disabled: boolean) => ManifestDownload;
14
+ export declare const useFileManifestDownload: (filters: Filters) => ManifestDownload;
@@ -1,4 +1,3 @@
1
- import { useEffect } from "react";
2
1
  import { MANIFEST_DOWNLOAD_FORMAT } from "../../apis/azul/common/entities";
3
2
  import { BULK_DOWNLOAD_EXECUTION_ENVIRONMENT } from "../../components/Export/common/entities";
4
3
  import { useCatalog } from "../useCatalog";
@@ -8,10 +7,9 @@ import { buildRequestManifest } from "../useRequestManifest/utils";
8
7
  /**
9
8
  * Returns file manifest download url and file name.
10
9
  * @param filters - Filters.
11
- * @param disabled - Disabled.
12
10
  * @returns file manifest download url and file name.
13
11
  */
14
- export const useFileManifestDownload = (filters, disabled) => {
12
+ export const useFileManifestDownload = (filters) => {
15
13
  // Retrieve the endpoint URL from configured data source.
16
14
  const config = useConfig();
17
15
  const endpointUrl = config.config.dataSource.url;
@@ -19,20 +17,15 @@ export const useFileManifestDownload = (filters, disabled) => {
19
17
  const catalog = useCatalog(); // catalog should be defined.
20
18
  // Build request manifest request URL.
21
19
  const { requestMethod, requestUrl } = buildRequestManifest(endpointUrl, catalog, filters, MANIFEST_DOWNLOAD_FORMAT.COMPACT);
22
- const { data, isIdle, isLoading, run } = useRequestFileLocation(requestUrl, requestMethod);
20
+ const { data, isIdle, isLoading, run: requestManifest, } = useRequestFileLocation(requestUrl, requestMethod);
23
21
  const manifestURL = getManifestDownloadURL(data);
24
22
  const fileName = getManifestDownloadFileName(data);
25
- // Requests file manifest.
26
- useEffect(() => {
27
- if (disabled)
28
- return;
29
- run();
30
- }, [disabled, requestUrl, run]);
31
23
  return {
32
24
  fileName,
33
25
  isIdle,
34
26
  isLoading,
35
27
  manifestURL,
28
+ requestManifest,
36
29
  };
37
30
  };
38
31
  /**
@@ -1,14 +1,15 @@
1
1
  import { Filters } from "../../common/entities";
2
2
  export interface ManifestSpreadsheet {
3
- exists?: boolean;
4
- fileFormat?: string;
5
3
  fileName?: string;
6
4
  fileUrl?: string;
5
+ isIdle?: boolean;
6
+ isLoading?: boolean;
7
+ requestManifest?: () => void;
8
+ spreadsheetUrl?: string;
7
9
  }
8
10
  /**
9
11
  * Returns file manifest spreadsheet.
10
12
  * @param filters - Filters.
11
- * @param disabled - Disabled.
12
13
  * @returns file manifest spreadsheet.
13
14
  */
14
- export declare const useFileManifestSpreadsheet: (filters: Filters, disabled: boolean) => ManifestSpreadsheet | undefined;
15
+ export declare const useFileManifestSpreadsheet: (filters: Filters) => Omit<ManifestSpreadsheet, "fileUrl">;
@@ -1,17 +1,17 @@
1
- import { useEffect, useMemo } from "react";
1
+ import { useCallback, useEffect, useMemo } from "react";
2
2
  import { APIEndpoints, } from "../../apis/azul/common/entities";
3
3
  import { fetchEntitiesFromURL } from "../../entity/common/service";
4
4
  import { fetchQueryParams } from "../../utils/fetchQueryParams";
5
5
  import { useAsync } from "../useAsync";
6
6
  import { useCatalog } from "../useCatalog";
7
7
  import { useFetchRequestURL } from "../useFetchRequestURL";
8
+ import { useRequestFileLocation } from "../useRequestFileLocation";
8
9
  /**
9
10
  * Returns file manifest spreadsheet.
10
11
  * @param filters - Filters.
11
- * @param disabled - Disabled.
12
12
  * @returns file manifest spreadsheet.
13
13
  */
14
- export const useFileManifestSpreadsheet = (filters, disabled) => {
14
+ export const useFileManifestSpreadsheet = (filters) => {
15
15
  // Determine catalog.
16
16
  const catalog = useCatalog(); // catalog should be defined.
17
17
  // Build request params.
@@ -19,16 +19,26 @@ export const useFileManifestSpreadsheet = (filters, disabled) => {
19
19
  // Build request URL.
20
20
  const requestURL = useFetchRequestURL(APIEndpoints.FILES, requestParams);
21
21
  // Fetch files to determine if file exists.
22
- const { data, run } = useAsync();
22
+ const { data: files, isIdle, isLoading: isFilesLoading, run: requestFiles, } = useAsync();
23
23
  // Grab manifest spreadsheet.
24
- const manifestSpreadsheet = useMemo(() => getManifestSpreadsheet(data?.hits), [data]);
24
+ const { fileName, fileUrl } = useMemo(() => getManifestSpreadsheet(files?.hits), [files]);
25
+ // Fetch file manifest.
26
+ const { data, isLoading, run } = useRequestFileLocation(fileUrl);
25
27
  // Fetch response from files endpoint.
28
+ const requestManifest = useCallback(() => {
29
+ requestFiles(fetchEntitiesFromURL(requestURL, undefined));
30
+ }, [requestFiles, requestURL]);
31
+ // Fetch file manifest.
26
32
  useEffect(() => {
27
- if (disabled)
28
- return;
29
- run(fetchEntitiesFromURL(requestURL, undefined));
30
- }, [disabled, requestURL, run]);
31
- return manifestSpreadsheet;
33
+ run();
34
+ }, [fileUrl, run]);
35
+ return {
36
+ fileName,
37
+ isIdle,
38
+ isLoading: isFilesLoading || isLoading,
39
+ requestManifest,
40
+ spreadsheetUrl: data?.location,
41
+ };
32
42
  };
33
43
  /**
34
44
  * Prepend "/fetch" to the path of the specified file URL, if not already included.
@@ -52,20 +62,14 @@ function buildFetchFileUrl(fileUrl) {
52
62
  * @returns manifest spreadsheet.
53
63
  */
54
64
  function getManifestSpreadsheet(files) {
55
- if (!files) {
56
- return;
57
- }
65
+ if (!files)
66
+ return { fileName: undefined, fileUrl: undefined };
58
67
  // Handle case where file does not exist.
59
- if (files.length === 0) {
60
- return {
61
- exists: false,
62
- };
63
- }
68
+ if (files.length === 0)
69
+ return { fileName: undefined, fileUrl: undefined };
64
70
  // Project manifest spreadsheet exists.
65
71
  const file = files[0];
66
72
  return {
67
- exists: true,
68
- fileFormat: file.files[0]?.format,
69
73
  fileName: file.files[0]?.name,
70
74
  fileUrl: buildFetchFileUrl(file.files[0]?.url),
71
75
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databiosphere/findable-ui",
3
- "version": "35.1.0",
3
+ "version": "35.2.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
@@ -1,18 +1,12 @@
1
- import {
2
- ButtonBase,
3
- TableBody,
4
- TableCell,
5
- TableRow,
6
- Tooltip,
7
- } from "@mui/material";
1
+ import { Button, TableBody, TableCell, TableRow, Tooltip } from "@mui/material";
8
2
  import copy from "copy-to-clipboard";
9
- import React, { useRef } from "react";
3
+ import React from "react";
10
4
  import { Filters } from "../../../../../../../../common/entities";
11
5
  import { useDownloadStatus } from "../../../../../../../../hooks/useDownloadStatus";
12
6
  import { useFileManifestDownload } from "../../../../../../../../hooks/useFileManifest/useFileManifestDownload";
13
7
  import { useLoginGuard } from "../../../../../../../../providers/loginGuard/hook";
8
+ import { BUTTON_PROPS } from "../../../../../../../common/Button/constants";
14
9
  import { ButtonGroup } from "../../../../../../../common/ButtonGroup/buttonGroup";
15
- import { ButtonGroupButton } from "../../../../../../../common/ButtonGroup/components/ButtonGroupButton/buttonGroupButton";
16
10
  import {
17
11
  ContentCopyIconSmall,
18
12
  DownloadIconSmall,
@@ -26,6 +20,7 @@ import {
26
20
  LOADING_PANEL_STYLE,
27
21
  } from "../../../../../../../Loading/loading";
28
22
  import { GridTable } from "../../../../../../../Table/common/gridTable.styles";
23
+ import { TOOLTIP_PROPS } from "../../constants";
29
24
  import {
30
25
  SectionTitle,
31
26
  TableContainer,
@@ -38,87 +33,64 @@ export interface FileManifestDownloadProps {
38
33
  export const FileManifestDownload = ({
39
34
  filters,
40
35
  }: FileManifestDownloadProps): JSX.Element => {
41
- const downloadRef = useRef<HTMLAnchorElement>(null);
42
36
  const { disabled, message } = useDownloadStatus();
43
- const { fileName, isIdle, isLoading, manifestURL } = useFileManifestDownload(
44
- filters,
45
- disabled
46
- );
47
- const isInProgress = (isIdle || isLoading) && !disabled;
48
- const isReady = Boolean(manifestURL) || disabled;
37
+ const { fileName, isIdle, isLoading, manifestURL, requestManifest } =
38
+ useFileManifestDownload(filters);
49
39
 
50
40
  // Prompt user for login before download and copy, if required.
51
41
  const { requireLogin } = useLoginGuard();
52
42
 
53
- // Copies file manifest.
54
- const copyManifestURL = (url?: string): void => {
55
- if (!url) return;
56
- copy(url);
57
- };
58
-
59
- // Downloads file manifest.
60
- const downloadManifestURL = (): void => {
61
- downloadRef.current?.click();
62
- };
63
-
64
43
  return (
65
44
  <FluidPaper>
66
45
  <GridPaper>
67
46
  <SectionTitle>File Manifest</SectionTitle>
68
47
  <TableContainer>
69
48
  <Loading
70
- loading={isInProgress}
49
+ loading={isLoading}
71
50
  panelStyle={LOADING_PANEL_STYLE.INHERIT}
72
51
  />
73
- <GridTable gridTemplateColumns={isReady ? "auto 1fr" : "1fr"}>
52
+ <GridTable gridTemplateColumns="auto 1fr">
74
53
  <TableBody>
75
54
  <TableRow>
76
- {isInProgress ? (
77
- <TableCell />
78
- ) : isReady ? (
55
+ {isIdle || isLoading ? (
56
+ <TableCell>
57
+ <Tooltip {...TOOLTIP_PROPS} title={message}>
58
+ <span>
59
+ <Button
60
+ {...BUTTON_PROPS.PRIMARY_CONTAINED}
61
+ disabled={disabled || isLoading}
62
+ onClick={() => requireLogin(requestManifest)}
63
+ >
64
+ Request link
65
+ </Button>
66
+ </span>
67
+ </Tooltip>
68
+ </TableCell>
69
+ ) : (
79
70
  <>
71
+ {manifestURL && (
72
+ <TableCell>
73
+ <ButtonGroup
74
+ Buttons={[
75
+ <Button key="download" download href={manifestURL}>
76
+ <DownloadIconSmall />
77
+ </Button>,
78
+ <Button
79
+ key="copy"
80
+ onClick={() => copy(manifestURL)}
81
+ >
82
+ <ContentCopyIconSmall />
83
+ </Button>,
84
+ ]}
85
+ />
86
+ </TableCell>
87
+ )}
80
88
  <TableCell>
81
- <ButtonBase
82
- disabled={disabled}
83
- href={manifestURL ?? ""}
84
- ref={downloadRef}
85
- sx={{ display: "none" }}
86
- />
87
- <Tooltip arrow title={message}>
88
- <span>
89
- <ButtonGroup
90
- Buttons={[
91
- <ButtonGroupButton
92
- key="download"
93
- action="Download file manifest"
94
- disabled={disabled}
95
- label={<DownloadIconSmall />}
96
- onClick={() =>
97
- requireLogin(downloadManifestURL)
98
- }
99
- />,
100
- <ButtonGroupButton
101
- key="copy"
102
- action="Copy file manifest"
103
- disabled={disabled}
104
- label={<ContentCopyIconSmall />}
105
- onClick={() =>
106
- requireLogin((): void =>
107
- copyManifestURL(manifestURL)
108
- )
109
- }
110
- />,
111
- ]}
112
- />
113
- </span>
114
- </Tooltip>
89
+ {manifestURL
90
+ ? fileName
91
+ : "The manifest is not available for this project."}
115
92
  </TableCell>
116
- <TableCell>{fileName}</TableCell>
117
93
  </>
118
- ) : (
119
- <TableCell>
120
- The manifest is not available for this project.
121
- </TableCell>
122
94
  )}
123
95
  </TableRow>
124
96
  </TableBody>
@@ -1,18 +1,11 @@
1
- import {
2
- ButtonBase,
3
- TableBody,
4
- TableCell,
5
- TableRow,
6
- Tooltip,
7
- } from "@mui/material";
1
+ import { Button, TableBody, TableCell, TableRow, Tooltip } from "@mui/material";
8
2
  import copy from "copy-to-clipboard";
9
- import React, { useEffect, useRef } from "react";
3
+ import React from "react";
10
4
  import { Filters } from "../../../../../../../../common/entities";
11
5
  import { useDownloadStatus } from "../../../../../../../../hooks/useDownloadStatus";
12
6
  import { useFileManifestSpreadsheet } from "../../../../../../../../hooks/useFileManifest/useFileManifestSpreadsheet";
13
- import { useRequestFileLocation } from "../../../../../../../../hooks/useRequestFileLocation";
7
+ import { BUTTON_PROPS } from "../../../../../../../common/Button/constants";
14
8
  import { ButtonGroup } from "../../../../../../../common/ButtonGroup/buttonGroup";
15
- import { ButtonGroupButton } from "../../../../../../../common/ButtonGroup/components/ButtonGroupButton/buttonGroupButton";
16
9
  import {
17
10
  ContentCopyIconSmall,
18
11
  DownloadIconSmall,
@@ -26,6 +19,7 @@ import {
26
19
  LOADING_PANEL_STYLE,
27
20
  } from "../../../../../../../Loading/loading";
28
21
  import { GridTable } from "../../../../../../../Table/common/gridTable.styles";
22
+ import { TOOLTIP_PROPS } from "../../constants";
29
23
  import {
30
24
  SectionTitle,
31
25
  TableContainer,
@@ -38,30 +32,14 @@ export interface FileManifestSpreadsheetProps {
38
32
  export const FileManifestSpreadsheet = ({
39
33
  filters,
40
34
  }: FileManifestSpreadsheetProps): JSX.Element => {
41
- const downloadRef = useRef<HTMLAnchorElement>(null);
42
35
  const { disabled, message } = useDownloadStatus();
43
- const { exists, fileName, fileUrl } =
44
- useFileManifestSpreadsheet(filters, disabled) || {};
45
- const { data, isLoading, run } = useRequestFileLocation(fileUrl);
46
- const spreadsheetURL = data?.location;
47
- const isInProgress = (exists === undefined || isLoading) && !disabled;
48
- const isReady = Boolean(spreadsheetURL) || disabled;
49
-
50
- // Copies metadata spreadsheet.
51
- const copyMetadataURL = (url?: string): void => {
52
- if (!url) return;
53
- copy(url);
54
- };
55
-
56
- // Downloads metadata spreadsheet.
57
- const downloadMetadataURL = (): void => {
58
- downloadRef.current?.click();
59
- };
60
-
61
- // Requests metadata spreadsheet.
62
- useEffect(() => {
63
- run();
64
- }, [fileUrl, run]);
36
+ const {
37
+ fileName,
38
+ isIdle = false,
39
+ isLoading = false,
40
+ requestManifest,
41
+ spreadsheetUrl,
42
+ } = useFileManifestSpreadsheet(filters) || {};
65
43
 
66
44
  return (
67
45
  <FluidPaper>
@@ -69,55 +47,55 @@ export const FileManifestSpreadsheet = ({
69
47
  <SectionTitle>Metadata</SectionTitle>
70
48
  <TableContainer>
71
49
  <Loading
72
- loading={isInProgress}
50
+ loading={isLoading}
73
51
  panelStyle={LOADING_PANEL_STYLE.INHERIT}
74
52
  />
75
- <GridTable gridTemplateColumns={isReady ? "auto 1fr" : "1fr"}>
53
+ <GridTable gridTemplateColumns="auto 1fr">
76
54
  <TableBody>
77
55
  <TableRow>
78
- {isInProgress ? (
79
- <TableCell />
80
- ) : isReady ? (
56
+ {isIdle || isLoading ? (
57
+ <TableCell>
58
+ <Tooltip {...TOOLTIP_PROPS} title={message}>
59
+ <span>
60
+ <Button
61
+ {...BUTTON_PROPS.PRIMARY_CONTAINED}
62
+ disabled={disabled || isLoading}
63
+ onClick={() => requestManifest?.()}
64
+ >
65
+ Request link
66
+ </Button>
67
+ </span>
68
+ </Tooltip>
69
+ </TableCell>
70
+ ) : (
81
71
  <>
72
+ {spreadsheetUrl && (
73
+ <TableCell>
74
+ <ButtonGroup
75
+ Buttons={[
76
+ <Button
77
+ key="download"
78
+ download
79
+ href={spreadsheetUrl}
80
+ >
81
+ <DownloadIconSmall />
82
+ </Button>,
83
+ <Button
84
+ key="copy"
85
+ onClick={() => copy(spreadsheetUrl)}
86
+ >
87
+ <ContentCopyIconSmall />
88
+ </Button>,
89
+ ]}
90
+ />
91
+ </TableCell>
92
+ )}
82
93
  <TableCell>
83
- <ButtonBase
84
- disabled={disabled}
85
- download
86
- href={spreadsheetURL ?? ""}
87
- ref={downloadRef}
88
- sx={{ display: "none" }}
89
- />
90
- <Tooltip arrow title={message}>
91
- <span>
92
- <ButtonGroup
93
- Buttons={[
94
- <ButtonGroupButton
95
- key="download"
96
- action="Download metadata spreadsheet"
97
- disabled={disabled}
98
- label={<DownloadIconSmall />}
99
- onClick={downloadMetadataURL}
100
- />,
101
- <ButtonGroupButton
102
- key="copy"
103
- action="Copy metadata spreadsheet"
104
- disabled={disabled}
105
- label={<ContentCopyIconSmall />}
106
- onClick={(): void =>
107
- copyMetadataURL(spreadsheetURL)
108
- }
109
- />,
110
- ]}
111
- />
112
- </span>
113
- </Tooltip>
94
+ {spreadsheetUrl
95
+ ? fileName
96
+ : "The metadata is not available for this project."}
114
97
  </TableCell>
115
- <TableCell>{fileName}</TableCell>
116
98
  </>
117
- ) : (
118
- <TableCell>
119
- The metadata is not available for this project.
120
- </TableCell>
121
99
  )}
122
100
  </TableRow>
123
101
  </TableBody>
@@ -0,0 +1,24 @@
1
+ import { TooltipProps } from "@mui/material";
2
+
3
+ export const TOOLTIP_PROPS: Partial<TooltipProps> = {
4
+ arrow: true,
5
+ slotProps: {
6
+ popper: {
7
+ modifiers: [
8
+ {
9
+ name: "offset",
10
+ options: {
11
+ offset: [0, -4],
12
+ },
13
+ },
14
+ {
15
+ name: "preventOverflow",
16
+ options: { padding: 8 },
17
+ },
18
+ ],
19
+ },
20
+ tooltip: {
21
+ sx: { maxWidth: "none" },
22
+ },
23
+ },
24
+ };
@@ -0,0 +1,16 @@
1
+ import styled from "@emotion/styled";
2
+ import { Chip } from "@mui/material";
3
+ import { PALETTE } from "../../../../../../styles/common/constants/palette";
4
+
5
+ export const StyledChip = styled(Chip)`
6
+ border-radius: 4px;
7
+ box-shadow: 0 0 0 2px ${PALETTE.COMMON_WHITE};
8
+ height: auto;
9
+ justify-self: flex-start;
10
+ min-width: 0;
11
+
12
+ .MuiChip-label {
13
+ padding: 2px 8px;
14
+ white-space: normal;
15
+ }
16
+ `;
@@ -0,0 +1,21 @@
1
+ import { CellContext, RowData } from "@tanstack/react-table";
2
+ import React from "react";
3
+ import { CHIP_PROPS } from "../../../../../../styles/common/mui/chip";
4
+ import { BaseComponentProps } from "../../../../../types";
5
+ import { StyledChip } from "./codeCell.styles";
6
+
7
+ export const CodeCell = <T extends RowData, TValue extends string = string>({
8
+ className,
9
+ getValue,
10
+ }: BaseComponentProps & CellContext<T, TValue>): JSX.Element | null => {
11
+ const value = getValue();
12
+ if (!value) return null;
13
+ return (
14
+ <StyledChip
15
+ className={className}
16
+ color={CHIP_PROPS.COLOR.DEFAULT}
17
+ label={value}
18
+ size={CHIP_PROPS.SIZE.SMALL}
19
+ />
20
+ );
21
+ };
@@ -1,4 +1,3 @@
1
- import { useEffect } from "react";
2
1
  import { MANIFEST_DOWNLOAD_FORMAT } from "../../apis/azul/common/entities";
3
2
  import { Filters } from "../../common/entities";
4
3
  import { BULK_DOWNLOAD_EXECUTION_ENVIRONMENT } from "../../components/Export/common/entities";
@@ -15,18 +14,15 @@ export interface ManifestDownload {
15
14
  isIdle: boolean;
16
15
  isLoading: boolean;
17
16
  manifestURL?: string;
17
+ requestManifest: () => void;
18
18
  }
19
19
 
20
20
  /**
21
21
  * Returns file manifest download url and file name.
22
22
  * @param filters - Filters.
23
- * @param disabled - Disabled.
24
23
  * @returns file manifest download url and file name.
25
24
  */
26
- export const useFileManifestDownload = (
27
- filters: Filters,
28
- disabled: boolean
29
- ): ManifestDownload => {
25
+ export const useFileManifestDownload = (filters: Filters): ManifestDownload => {
30
26
  // Retrieve the endpoint URL from configured data source.
31
27
  const config = useConfig();
32
28
  const endpointUrl = config.config.dataSource.url;
@@ -39,24 +35,21 @@ export const useFileManifestDownload = (
39
35
  filters,
40
36
  MANIFEST_DOWNLOAD_FORMAT.COMPACT
41
37
  );
42
- const { data, isIdle, isLoading, run } = useRequestFileLocation(
43
- requestUrl,
44
- requestMethod
45
- );
38
+ const {
39
+ data,
40
+ isIdle,
41
+ isLoading,
42
+ run: requestManifest,
43
+ } = useRequestFileLocation(requestUrl, requestMethod);
46
44
  const manifestURL = getManifestDownloadURL(data);
47
45
  const fileName = getManifestDownloadFileName(data);
48
46
 
49
- // Requests file manifest.
50
- useEffect(() => {
51
- if (disabled) return;
52
- run();
53
- }, [disabled, requestUrl, run]);
54
-
55
47
  return {
56
48
  fileName,
57
49
  isIdle,
58
50
  isLoading,
59
51
  manifestURL,
52
+ requestManifest,
60
53
  };
61
54
  };
62
55
 
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo } from "react";
1
+ import { useCallback, useEffect, useMemo } from "react";
2
2
  import {
3
3
  APIEndpoints,
4
4
  AzulEntitiesResponse,
@@ -9,24 +9,25 @@ import { fetchQueryParams } from "../../utils/fetchQueryParams";
9
9
  import { useAsync } from "../useAsync";
10
10
  import { useCatalog } from "../useCatalog";
11
11
  import { useFetchRequestURL } from "../useFetchRequestURL";
12
+ import { useRequestFileLocation } from "../useRequestFileLocation";
12
13
 
13
14
  export interface ManifestSpreadsheet {
14
- exists?: boolean;
15
- fileFormat?: string;
16
15
  fileName?: string;
17
16
  fileUrl?: string;
17
+ isIdle?: boolean;
18
+ isLoading?: boolean;
19
+ requestManifest?: () => void;
20
+ spreadsheetUrl?: string;
18
21
  }
19
22
 
20
23
  /**
21
24
  * Returns file manifest spreadsheet.
22
25
  * @param filters - Filters.
23
- * @param disabled - Disabled.
24
26
  * @returns file manifest spreadsheet.
25
27
  */
26
28
  export const useFileManifestSpreadsheet = (
27
- filters: Filters,
28
- disabled: boolean
29
- ): ManifestSpreadsheet | undefined => {
29
+ filters: Filters
30
+ ): Omit<ManifestSpreadsheet, "fileUrl"> => {
30
31
  // Determine catalog.
31
32
  const catalog = useCatalog() as string; // catalog should be defined.
32
33
  // Build request params.
@@ -34,20 +35,39 @@ export const useFileManifestSpreadsheet = (
34
35
  // Build request URL.
35
36
  const requestURL = useFetchRequestURL(APIEndpoints.FILES, requestParams);
36
37
  // Fetch files to determine if file exists.
37
- const { data, run } = useAsync<AzulEntitiesResponse>();
38
+ const {
39
+ data: files,
40
+ isIdle,
41
+ isLoading: isFilesLoading,
42
+ run: requestFiles,
43
+ } = useAsync<AzulEntitiesResponse>();
44
+
38
45
  // Grab manifest spreadsheet.
39
- const manifestSpreadsheet = useMemo(
40
- () => getManifestSpreadsheet(data?.hits),
41
- [data]
46
+ const { fileName, fileUrl } = useMemo(
47
+ () => getManifestSpreadsheet(files?.hits),
48
+ [files]
42
49
  );
43
50
 
51
+ // Fetch file manifest.
52
+ const { data, isLoading, run } = useRequestFileLocation(fileUrl);
53
+
44
54
  // Fetch response from files endpoint.
55
+ const requestManifest = useCallback(() => {
56
+ requestFiles(fetchEntitiesFromURL(requestURL, undefined));
57
+ }, [requestFiles, requestURL]);
58
+
59
+ // Fetch file manifest.
45
60
  useEffect(() => {
46
- if (disabled) return;
47
- run(fetchEntitiesFromURL(requestURL, undefined));
48
- }, [disabled, requestURL, run]);
61
+ run();
62
+ }, [fileUrl, run]);
49
63
 
50
- return manifestSpreadsheet;
64
+ return {
65
+ fileName,
66
+ isIdle,
67
+ isLoading: isFilesLoading || isLoading,
68
+ requestManifest,
69
+ spreadsheetUrl: data?.location,
70
+ };
51
71
  };
52
72
 
53
73
  /**
@@ -74,21 +94,16 @@ function buildFetchFileUrl(fileUrl?: string): string | undefined {
74
94
  */
75
95
  function getManifestSpreadsheet(
76
96
  files?: AzulEntitiesResponse["hits"]
77
- ): ManifestSpreadsheet | undefined {
78
- if (!files) {
79
- return;
80
- }
97
+ ): Pick<ManifestSpreadsheet, "fileName" | "fileUrl"> {
98
+ if (!files) return { fileName: undefined, fileUrl: undefined };
99
+
81
100
  // Handle case where file does not exist.
82
- if (files.length === 0) {
83
- return {
84
- exists: false,
85
- };
86
- }
101
+ if (files.length === 0) return { fileName: undefined, fileUrl: undefined };
102
+
87
103
  // Project manifest spreadsheet exists.
88
104
  const file = files[0];
105
+
89
106
  return {
90
- exists: true,
91
- fileFormat: file.files[0]?.format,
92
107
  fileName: file.files[0]?.name,
93
108
  fileUrl: buildFetchFileUrl(file.files[0]?.url),
94
109
  };