Skip to content

Commit

Permalink
Merge pull request #1348 from danskernesdigitalebibliotek/DDFLSBP-722…
Browse files Browse the repository at this point in the history
…-availability-labels-viser-udlant-under-udgaver-skont-bogen-er-hjemme

Availability bug
  • Loading branch information
spaceo authored Aug 6, 2024
2 parents 6973e40 + db378df commit a51c07b
Show file tree
Hide file tree
Showing 11 changed files with 731 additions and 162 deletions.
61 changes: 44 additions & 17 deletions src/apps/material/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,32 +475,59 @@ export const isParallelReservation = (manifestations: Manifestation[]) =>
hasCorrectAccessType(AccessTypeCode.Physical, manifestations) &&
!isArticle(manifestations);

type BlacklistType = "availability" | "pickup" | "both";

const formatBranches = (branches: string[][]) => {
return branches.flat().length ? { exclude: branches.flat() } : {};
};

const branchesFromConfig = (
blacklist: BlacklistType,
config: UseConfigFunction
) => {
const configMap: Record<Exclude<BlacklistType, "both">, string> = {
availability: "blacklistedAvailabilityBranchesConfig",
pickup: "blacklistedPickupBranchesConfig"
};

type ConfigMapKey = keyof typeof configMap;

if (!configMap[blacklist as ConfigMapKey]) {
return [];
}

return config(configMap[blacklist as ConfigMapKey], {
transformer: "stringToArray"
}).filter((branch) => branch);
};

// Because we need to exclude the branches that are blacklisted, we need to
// use a custom hook to prevent duplicate code
export const getBlacklistedQueryArgs = (
faustIds: FaustId[],
config: UseConfigFunction,
blacklist: "availability" | "pickup" | "both"
blacklistType: BlacklistType
) => {
const configKey = {
availability: "blacklistedAvailabilityBranchesConfig",
pickup: "blacklistedPickupBranchesConfig",
both: "blacklistedAvailabilityBranchesConfig"
// Fixed arguments for the query.
const args = {
recordid: faustIds
};
let blacklistBranches = config(configKey[blacklist], {
transformer: "stringToArray"
});
// If we want to blacklist both availability and pickup branches we now add the
// complimentary blacklist
if (blacklist === "both") {
const additionalBlacklistBranches = config(configKey.pickup, {
transformer: "stringToArray"
});
blacklistBranches = blacklistBranches.concat(additionalBlacklistBranches);
// Return query args with the either availability or pickup branches excluded.
if (blacklistType !== "both") {
return {
...args,
...formatBranches([branchesFromConfig(blacklistType, config)])
};
}

// If we want to blacklist both availability and pickup branches
// return query args with both blacklist types excluded.
return {
recordid: faustIds,
...(blacklistBranches ? { exclude: blacklistBranches } : {})
...args,
...formatBranches([
branchesFromConfig("availability", config),
branchesFromConfig("pickup", config)
])
};
};

Expand Down
3 changes: 2 additions & 1 deletion src/components/availability-label/availability-label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { useText } from "../../core/utils/text";
import LinkNoStyle from "../atoms/links/LinkNoStyle";
import { useStatistics } from "../../core/statistics/useStatistics";
import { statistics } from "../../core/statistics/statistics";
import { getParentAvailabilityLabelClass, useAvailabilityData } from "./helper";
import { getParentAvailabilityLabelClass } from "./helper";
import {
Access,
AccessTypeCode
} from "../../core/dbc-gateway/generated/graphql";
import AvailabilityLabelInside from "./availability-label-inside";
import { FaustId } from "../../core/utils/types/ids";
import useAvailabilityData from "./useAvailabilityData";

export interface AvailabilityLabelProps {
manifestText: string;
Expand Down
146 changes: 8 additions & 138 deletions src/components/availability-label/helper.ts
Original file line number Diff line number Diff line change
@@ -1,148 +1,18 @@
import { useEffect, useState } from "react";
import clsx from "clsx";
import {
useGetV1LoanstatusIdentifier,
useGetV1ProductsIdentifier
} from "../../core/publizon/publizon";
import { useConfig } from "../../core/utils/config";
import {
Access,
AccessTypeCode
} from "../../core/dbc-gateway/generated/graphql";
import { FaustId } from "../../core/utils/types/ids";
import useGetAvailability from "../../core/utils/useGetAvailability";
import { publizonProductStatuses } from "./types";
import { ManifestationMaterialType } from "../../core/utils/types/material-type";

export const useAvailabilityData = ({
accessTypes,
access,
faustIds,
isbn,
manifestText
}: {
accessTypes: AccessTypeCode[];
access: Access["__typename"][];
faustIds: FaustId[] | null;
isbn: string | null;
manifestText: string;
}) => {
const [isAvailable, setIsAvailable] = useState<null | boolean>(null);
const config = useConfig();
const isOnline = accessTypes?.includes(AccessTypeCode.Online) ?? false;
const [isCostFree, setIsCostFree] = useState<null | boolean>(null);
const [isLoading, setIsLoading] = useState<null | boolean>(null);

useEffect(() => {
// An online material is by default always available.
if (isOnline) {
setIsAvailable(true);
}
}, [isOnline]);

useEffect(() => {
// Articles are always available.
if (
manifestText === ManifestationMaterialType.article &&
isAvailable === null
) {
setIsAvailable(true);
}
}, [manifestText, isAvailable]);

const { isLoading: isLoadingIdentifier } = useGetV1ProductsIdentifier(
isbn ?? "",
{
query: {
// Publizon / useGetV1ProductsIdentifier is responsible for online
// materials. It requires an ISBN to do lookups.
enabled:
isOnline &&
isbn !== null &&
manifestText !== ManifestationMaterialType.article,
onSuccess: (res) => {
// If an online material isn't cost-free we need to check whether there is a queue
// to reserve it (via useGetV1LoanstatusIdentifier below in the code)
if (res?.product?.costFree === false) {
setIsCostFree(false);
return;
}
setIsCostFree(true);
}
}
}
);

const { isLoading: isLoadingProductInfo } = useGetV1LoanstatusIdentifier(
isbn || "",
{
// Publizon / useGetV1LoanstatusIdentifier shows loan status per material.
// This status is only available for products found on Ereol. Other online
// materials are always supposed to be shown as "available"
enabled:
isOnline &&
!!isbn &&
isCostFree === false &&
access.some((acc) => acc === "Ereol") &&
manifestText !== ManifestationMaterialType.article,
onSuccess: (res) => {
if (res && res.loanStatus) {
setIsAvailable(publizonProductStatuses[res.loanStatus].isAvailable);
return;
}
// In case the load status data doesn't exist we assume it isn't available
setIsAvailable(false);
}
}
);

const { isLoading: isLoadingAvailability } = useGetAvailability({
faustIds: faustIds ?? [],
config,
options: {
query: {
// FBS / useGetAvailabilityV3 is responsible for handling availability
// for physical items. This will be the majority of all materials so we
// use this for everything except materials that are explicitly online.
enabled:
!isOnline &&
faustIds !== null &&
manifestText !== ManifestationMaterialType.article,
onSuccess: (data) => {
if (data?.some((item) => item.available)) {
setIsAvailable(true);
}
}
}
}
});

useEffect(() => {
// Articles are always available, thus no need to load.
if (manifestText !== ManifestationMaterialType.article) {
setIsLoading(
(isLoadingAvailability ||
isLoadingIdentifier ||
isLoadingProductInfo) &&
isAvailable === null
);
}
}, [
isLoadingAvailability,
isLoadingIdentifier,
isLoadingProductInfo,
isAvailable,
manifestText
]);

return { isLoading, isAvailable };
};
import { AccessTypeCode } from "../../core/dbc-gateway/generated/graphql";
import articleTypes from "../../core/utils/types/article-types";

type GetParrentAvailabilityLabelClassProps = {
selected?: boolean;
cursorPointer?: boolean;
};

export const isOnline = (accessTypes: AccessTypeCode[]): boolean =>
accessTypes?.includes(AccessTypeCode.Online) ?? false;

export const isArticle = (manifestText: string): boolean =>
articleTypes.some((type) => manifestText.toLowerCase() === type);

export const getParentAvailabilityLabelClass = ({
selected,
cursorPointer
Expand Down
43 changes: 43 additions & 0 deletions src/components/availability-label/useAvailabilityData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
Access,
AccessTypeCode
} from "../../core/dbc-gateway/generated/graphql";
import { FaustId } from "../../core/utils/types/ids";
import { isOnline } from "./helper";
import useOnlineAvailabilityData from "./useOnlineAvailabilityData";
import usePhysicalAvailabilityData from "./usePhysicalAvailabilityData";

const useAvailabilityData = ({
accessTypes,
access,
faustIds,
manifestText,
isbn
}: {
accessTypes: AccessTypeCode[];
access: Access["__typename"][];
faustIds: FaustId[];
manifestText: string;
isbn: string | null;
}) => {
const availabilityOnline = useOnlineAvailabilityData({
enabled: isOnline(accessTypes),
access,
faustIds,
isbn
});

const availabilityPhysical = usePhysicalAvailabilityData({
enabled: !isOnline(accessTypes),
faustIds,
manifestText
});

if (isOnline(accessTypes)) {
return availabilityOnline;
}

return availabilityPhysical;
};

export default useAvailabilityData;
96 changes: 96 additions & 0 deletions src/components/availability-label/useOnlineAvailabilityData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useEffect, useState } from "react";
import {
useGetV1LoanstatusIdentifier,
useGetV1ProductsIdentifier
} from "../../core/publizon/publizon";
import { Access } from "../../core/dbc-gateway/generated/graphql";
import { FaustId } from "../../core/utils/types/ids";
import { publizonProductStatuses } from "./types";

const useOnlineAvailabilityData = ({
enabled,
access,
faustIds,
isbn
}: {
enabled: boolean;
access: Access["__typename"][];
faustIds: FaustId[] | null;
isbn: string | null;
}) => {
const [isAvailable, setIsAvailable] = useState<null | boolean>(null);

// Find out if the material is cost free.
const { isLoading: isLoadingIdentifier, data: dataIdentifier } =
useGetV1ProductsIdentifier(isbn ?? "", {
query: {
// Publizon / useGetV1ProductsIdentifier is responsible for online
// materials. It requires an ISBN to do lookups.
enabled: enabled && isAvailable === null && isbn !== null
}
});

// Ereol request.
const { isLoading: isLoadingEreolData, data: dataEreol } =
useGetV1LoanstatusIdentifier(isbn || "", {
// Publizon / useGetV1LoanstatusIdentifier shows loan status per material.
// This status is only available for products found on Ereol. Other online
// materials are always supposed to be shown as "available"
enabled:
enabled &&
isAvailable === null &&
!!isbn &&
// If the material is free (I think it is called blue material btw.)
// we should not load the loan status because then we know that it is available.
// So If the material is not free and we know it is an "Ereol" material we should load the loan status.
dataIdentifier?.product?.costFree === false &&
access.some((acc) => acc === "Ereol")
});

useEffect(() => {
if (
!enabled ||
isAvailable !== null ||
isLoadingIdentifier !== false ||
isLoadingEreolData !== false
) {
return;
}

// If we have ereol data, we can use that to determine the availability.
if (dataEreol && dataEreol.loanStatus) {
setIsAvailable(publizonProductStatuses[dataEreol.loanStatus].isAvailable);
}
}, [
isLoadingIdentifier,
isAvailable,
faustIds,
enabled,
dataEreol,
isLoadingEreolData
]);

// If hook is not enabled make it clear that the loading and availability status is unknown.
if (!enabled) {
return {
isLoading: null,
isAvailable: null
};
}

// An online material is by default always available if the availability status has not been set yet.
if (isAvailable === null) {
return {
isLoading: false,
isAvailable: true
};
}

// Return the availability status.
return {
isLoading: isLoadingIdentifier && isLoadingEreolData,
isAvailable
};
};

export default useOnlineAvailabilityData;
Loading

0 comments on commit a51c07b

Please sign in to comment.