Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Filters } from "../../../../common/entities";
import { useExploreState } from "../../../../hooks/useExploreState";
import { FileManifestType } from "../../../../hooks/useFileManifest/common/entities";
import { useFileManifest } from "../../../../hooks/useFileManifest/useFileManifest";
import { useFileManifestFileCount } from "../../../../hooks/useFileManifest/useFileManifestFileCount";
import {
FileLocation,
useRequestFileLocation,
Expand All @@ -30,6 +31,7 @@ interface DownloadCurlCommandProps {
filters: Filters; // Initializes bulk download filters.
formFacet: FormFacet;
manifestDownloadFormat?: ManifestDownloadFormat;
speciesFacetName: string;
}

export const DownloadCurlCommand = ({
Expand All @@ -41,8 +43,10 @@ export const DownloadCurlCommand = ({
filters,
formFacet,
manifestDownloadFormat = MANIFEST_DOWNLOAD_FORMAT.CURL,
speciesFacetName,
}: DownloadCurlCommandProps): JSX.Element => {
useFileManifest(filters, fileSummaryFacetName);
useFileManifestFileCount(filters, speciesFacetName, fileSummaryFacetName);
const [executionEnvironment, setExecutionEnvironment] =
useState<ExecutionEnvironment>(BULK_DOWNLOAD_EXECUTION_ENVIRONMENT.BASH);
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useExploreState } from "../../../../hooks/useExploreState";
import { useExportToTerraResponseURL } from "../../../../hooks/useExportToTerraResponseURL";
import { FileManifestType } from "../../../../hooks/useFileManifest/common/entities";
import { useFileManifest } from "../../../../hooks/useFileManifest/useFileManifest";
import { useFileManifestFileCount } from "../../../../hooks/useFileManifest/useFileManifestFileCount";
import { useFileManifestFormat } from "../../../../hooks/useFileManifest/useFileManifestFormat";
import { useRequestFileLocation } from "../../../../hooks/useRequestFileLocation";
import { useRequestManifest } from "../../../../hooks/useRequestManifest/useRequestManifest";
Expand All @@ -24,6 +25,7 @@ export interface ExportToTerraProps {
formFacet: FormFacet;
manifestDownloadFormat?: ManifestDownloadFormat;
manifestDownloadFormats: ManifestDownloadFormat[];
speciesFacetName: string;
}

export const ExportToTerra = ({
Expand All @@ -36,11 +38,13 @@ export const ExportToTerra = ({
formFacet,
manifestDownloadFormat,
manifestDownloadFormats,
speciesFacetName,
}: ExportToTerraProps): JSX.Element => {
const {
exploreState: { tabValue: entityList },
} = useExploreState();
useFileManifest(filters, fileSummaryFacetName);
useFileManifestFileCount(filters, speciesFacetName, fileSummaryFacetName);
const fileManifestFormatState = useFileManifestFormat(manifestDownloadFormat);
const { requestMethod, requestParams, requestUrl } = useRequestManifest(
fileManifestFormatState.fileManifestFormat,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Filters } from "../../../../common/entities";
import { useExploreState } from "../../../../hooks/useExploreState";
import { FileManifestType } from "../../../../hooks/useFileManifest/common/entities";
import { useFileManifest } from "../../../../hooks/useFileManifest/useFileManifest";
import { useFileManifestFileCount } from "../../../../hooks/useFileManifest/useFileManifestFileCount";
import {
FileLocation,
useRequestFileLocation,
Expand All @@ -25,6 +26,7 @@ export interface ManifestDownloadProps {
manifestDownloadFormat?: ManifestDownloadFormat;
ManifestDownloadStart: ElementType;
ManifestDownloadSuccess: ElementType;
speciesFacetName: string;
}

export const ManifestDownload = ({
Expand All @@ -36,8 +38,10 @@ export const ManifestDownload = ({
manifestDownloadFormat = MANIFEST_DOWNLOAD_FORMAT.COMPACT,
ManifestDownloadStart,
ManifestDownloadSuccess,
speciesFacetName,
}: ManifestDownloadProps): JSX.Element => {
useFileManifest(filters, fileSummaryFacetName);
useFileManifestFileCount(filters, speciesFacetName, fileSummaryFacetName);
const {
exploreState: { tabValue: entityList },
} = useExploreState();
Expand Down
65 changes: 65 additions & 0 deletions src/hooks/useFileManifest/useFileManifestFileCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useEffect, useState } from "react";
import { Filters } from "../../common/entities";
import { FileManifestActionKind } from "../../providers/fileManifestState";
import { useCatalog } from "../useCatalog";
import { useFileManifestState } from "../useFileManifestState";
import { useFetchSummary } from "./useFetchSummary";

/**
* Fetches the latest file count from the summary endpoint, excluding filters
* that match the provided species and file facet names, and updates the file manifest state.
*
* @param initialFilters - The initial set of filters to apply.
* @param speciesFacetName - The facet name representing species to exclude from the summary request.
* @param fileFacetName - The facet name representing file type to exclude from the summary request.
*/
export const useFileManifestFileCount = (
initialFilters: Filters | undefined,
speciesFacetName: string,
fileFacetName: string
): void => {
// Initial file manifest filter.
const [initFilters] = useState(() => initialFilters);

// File manifest dispatch.
const { fileManifestDispatch } = useFileManifestState();

// Determine catalog.
const catalog = useCatalog() as string; // catalog should be defined.

// Get filters required for fetching the summary.
const filters = excludeFacetsFromFilters(initFilters, [
speciesFacetName,
fileFacetName,
]);

// Fetch file count from summary.
const { summary: { fileCount } = {} } = useFetchSummary(
filters,
catalog,
true
);

useEffect(() => {
fileManifestDispatch({
payload: { fileCount },
type: FileManifestActionKind.UpdateFileCount,
});
}, [fileCount, fileManifestDispatch]);
};

/**
* Returns a new filters array with the specified facet names excluded.
*
* @param filters - The list of filters to process.
* @param facetNames - The facet names to exclude from the filters.
* @returns The filters array excluding any filters matching the provided facet names.
*/
function excludeFacetsFromFilters(
filters: Filters | undefined,
facetNames: string[]
): Filters {
return (filters || []).filter(
({ categoryKey }) => !facetNames.includes(categoryKey)
);
}
61 changes: 37 additions & 24 deletions src/hooks/useRequestManifest/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,41 @@ import { REQUEST_MANIFEST } from "./constants";
import { UseRequestManifest } from "./types";

/**
* Returns true if all form facets have all their terms selected.
* @param formFacet - Form related file facets.
* @returns true if all form facets have all their terms selected.
* Determines whether all files are selected by comparing the file count to the summary file count.
*
* @param state - The file manifest state object.
* @returns True if all files are selected; otherwise, false.
*/
export function areAllFormFilterTermsSelected(formFacet: FormFacet): boolean {
return Object.values(formFacet)
.filter(Boolean)
.every(
({ selectedTermCount, termCount }: FileFacet) =>
selectedTermCount === termCount
);
export function areAllFilesSelected(state: FileManifestState): boolean {
const { fileCount, summary } = state;

// Return false if file count or summary file count is undefined.
if (fileCount === undefined || summary?.fileCount === undefined) return false;

// Return true if file count equals summary file count.
return fileCount === summary.fileCount;
}

/**
* Generates the filters for a request URL based on the file manifest state.
* - **all form facets have all their terms selected** - returns filters from state without form filters.
* - **at least one form facet has an unselected term** - returns filters from state.
* @param state - File manifest state.
* @param formFacet - Form related file facets.
* @returns filters for the request URL.
* Builds the filters object for a request URL based on the file manifest state and form facets.
*
* - If all files are selected, returns filters from state excluding fully selected form filters.
* - If only some files are selected, returns the current filters from state.
*
* @param state - The file manifest state object.
* @param formFacet - The form-related file facets.
* @returns The filters to use for the request URL.
*/
export function buildRequestFilters(
state: FileManifestState,
formFacet: FormFacet
): Filters {
// Form terms are fully selected; return filters excluding form filters.
if (areAllFormFilterTermsSelected(formFacet)) {
// Return filters from state excluding form filters if all files are selected.
if (areAllFilesSelected(state)) {
return excludeFullySelectedFormFilters(state, formFacet);
}
// Form terms are partially selected; return filters.

// Return current filters from state.
return state.filters;
}

Expand Down Expand Up @@ -115,16 +120,24 @@ export function isCatalogReady(catalog: string | undefined): boolean {
}

/**
* Returns true if the file manifest state is ready for a request.
* A file manifest state is considered ready if it is both enabled (`isEnabled` is `true`)
* and not currently loading (`isLoading` is `false`).
* Determines if the file manifest state is ready to be used in a request.
*
* The state is considered ready when:
* - isEnabled is true
* - fileCount is defined
* - isLoading is false
*
* @param fileManifestState - File manifest state.
* @returns true if the file manifest state is ready for a request.
* @returns true if the file manifest state is ready.
*/
export function isFileManifestStateReady(
fileManifestState: FileManifestState
): boolean {
return fileManifestState.isEnabled && !fileManifestState.isLoading;
return (
fileManifestState.isEnabled &&
fileManifestState.fileCount !== undefined &&
!fileManifestState.isLoading
);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/mocks/useRequestFileManifest.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ export const FILTERS: Record<string, Filters> = {
};

export const FILE_MANIFEST_STATE = {
fileCount: 10,
filters: FILTERS.FORM_INITIAL_SET,
isEnabled: true,
isLoading: false,
summary: { fileCount: 10 },
} as FileManifestState;

export const FORM_FACET: Record<string, FormFacet> = {
Expand Down
23 changes: 23 additions & 0 deletions src/providers/fileManifestState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { FILE_MANIFEST_STATE } from "./fileManifestState/constants";
* File manifest state.
*/
export type FileManifestState = {
fileCount: number | undefined;
filesFacets: FileFacet[];
fileSummary?: AzulSummaryResponse;
fileSummaryFacetName?: string;
Expand Down Expand Up @@ -133,6 +134,7 @@ export function FileManifestStateProvider({
export enum FileManifestActionKind {
ClearFileManifest = "CLEAR_FILE_MANIFEST",
FetchFileManifest = "FETCH_FILE_MANIFEST",
UpdateFileCount = "UPDATE_FILE_COUNT",
UpdateFileManifest = "UPDATE_FILE_MANIFEST",
UpdateFilter = "UPDATE_FILTER",
UpdateFiltersCategory = "UPDATE_FILTERS_CATEGORY",
Expand All @@ -144,6 +146,7 @@ export enum FileManifestActionKind {
export type FileManifestAction =
| ClearFileManifestAction
| FetchFileManifestAction
| UpdateFileCountAction
| UpdateFileManifestAction
| UpdateFilterAction
| UpdateFiltersCategoryAction;
Expand Down Expand Up @@ -172,6 +175,14 @@ type UpdateFileManifestAction = {
type: FileManifestActionKind.UpdateFileManifest;
};

/**
* Update file count action.
*/
type UpdateFileCountAction = {
payload: UpdateFileCountPayload;
type: FileManifestActionKind.UpdateFileCount;
};

/**
* Update filter action.
*/
Expand All @@ -196,6 +207,13 @@ type FetchFileManifestPayload = {
filters: Filters;
};

/**
* Update file count payload.
*/
export type UpdateFileCountPayload = {
fileCount: number | undefined;
};

/**
* Update file manifest payload.
*/
Expand Down Expand Up @@ -235,6 +253,7 @@ function fileManifestReducer(
case FileManifestActionKind.ClearFileManifest: {
return {
...state,
fileCount: undefined,
isEnabled: false,
};
}
Expand All @@ -252,6 +271,10 @@ function fileManifestReducer(
isEnabled: true,
};
}
// Updates file count.
case FileManifestActionKind.UpdateFileCount: {
return { ...state, ...payload };
}
// Updates file manifest.
case FileManifestActionKind.UpdateFileManifest: {
return { ...state, ...payload };
Expand Down
1 change: 1 addition & 0 deletions src/providers/fileManifestState/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FileManifestState } from "../fileManifestState";

export const FILE_MANIFEST_STATE: FileManifestState = {
fileCount: undefined,
fileSummary: undefined,
fileSummaryFacetName: undefined,
fileSummaryFilters: [],
Expand Down
18 changes: 5 additions & 13 deletions tests/buildRequestFilters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,18 @@ describe("buildRequestFilters", () => {
expect(result).toEqual(fileManifestState.filters);
});

test("when at least one form facet has no terms selected", () => {
const fileManifestState = getFileManifestState(
FILTERS.FORM_INCOMPLETE_SET
);
test("when summary file count is not equal to initial file count", () => {
const fileManifestState = getFileManifestState(FILTERS.FORM_COMPLETE_SET);
const result = buildRequestFilters(
fileManifestState,
FORM_FACET.INCOMPLETE_SET
{ ...fileManifestState, summary: { fileCount: 9 } },
FORM_FACET.COMPLETE_SET
);
expect(result).toEqual(fileManifestState.filters);
});

test("when at least one form facet has an unselected term", () => {
const fileManifestState = getFileManifestState(FILTERS.FORM_SUBSET);
const result = buildRequestFilters(fileManifestState, FORM_FACET.SUBSET);
expect(result).toEqual(fileManifestState.filters);
});
});

describe("should return filters excluding form related filters", () => {
test("when all form facets have all their terms selected", () => {
test("when summary file count is equal to initial file count", () => {
const fileManifestState = getFileManifestState(FILTERS.FORM_COMPLETE_SET);
const result = buildRequestFilters(
fileManifestState,
Expand Down
10 changes: 10 additions & 0 deletions tests/useRequestManifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ describe("useRequestManifest", () => {
});
});

test("when fileManifestState fileCount is undefined", () => {
MOCK_USE_FILE_MANIFEST_STATE.mockReturnValue({
fileManifestDispatch: jest.fn(),
fileManifestState: { ...FILE_MANIFEST_STATE, fileCount: undefined },
});
testRequestManifest({
fileManifestFormat: MANIFEST_DOWNLOAD_FORMAT.VERBATIM_PFB,
});
});

describe("form selection is not ready", () => {
test("when a form facet is undefined", () => {
testRequestManifest({
Expand Down