Skip to content
Open
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
41 changes: 41 additions & 0 deletions src/components/common/ProcessingChip.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";
import Chip from "@mui/material/Chip";
import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline";
import SyncOutlinedIcon from "@mui/icons-material/SyncOutlined";
import { useTranslation } from "react-i18next";

const ProcessingChip = ({
processed = false,
fading = false,
fadeDurationMs = 60000,
sx,
...rest
}) => {
const { t } = useTranslation();

return (
<Chip
size="small"
variant="outlined"
color={processed ? "success" : "warning"}
icon={
processed ? (
<CheckCircleOutlineIcon fontSize="inherit" />
) : (
<SyncOutlinedIcon fontSize="inherit" />
)
}
label={processed ? t("common.processed") : t("common.processing")}
sx={[
{
opacity: fading ? 0 : 1,
transition: fading ? `opacity ${fadeDurationMs}ms linear` : undefined
},
...(Array.isArray(sx) ? sx : sx ? [sx] : [])
]}
{...rest}
/>
);
};

export default ProcessingChip;
178 changes: 175 additions & 3 deletions src/components/repos/CollectionVersionsTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ import {
} from "../../common/utils";
import { OperationsContext } from "../app/LayoutContext";
import DeleteEntityDialog from "../common/DeleteEntityDialog";
import ProcessingChip from "../common/ProcessingChip";
import ExpansionForm from "./ExpansionForm";
import ExpansionDetailsDialog from "./ExpansionDetailsDialog";
import RebuildExpansionDialog from "./RebuildExpansionDialog";

const PROCESSING_POLL_INTERVAL_MS = 10000;
const PROCESSED_CHIP_FADE_MS = 6000;

const isHeadVersion = version => (version?.version || version?.id) === "HEAD";

const isStaleExpansion = expansion =>
Expand All @@ -62,6 +66,8 @@ const isStaleExpansion = expansion =>
expansion?.extras?.stale
);

const isExpansionProcessing = expansion => Boolean(expansion?.is_processing);

const getVersionKey = version =>
version?.version_url || version?.url || version?.id;

Expand Down Expand Up @@ -214,6 +220,12 @@ const labelFromVersionUrl = url => {
return url;
};

const parseProcessingValue = value => {
if (typeof value === "boolean") return value;
if (typeof value === "string") return value.toLowerCase() === "true";
return Boolean(value);
};

const CollectionVersionsTab = ({
repo,
versions,
Expand Down Expand Up @@ -252,8 +264,13 @@ const CollectionVersionsTab = ({
});
const [repoUpdatesByExpansion, setRepoUpdatesByExpansion] = React.useState({});
const [dismissedUpdates, setDismissedUpdates] = React.useState(new Set());
const [processingStatusByExpansion, setProcessingStatusByExpansion] =
React.useState({});
const expansionRefs = React.useRef({});
const fetchedRepoUpdatesRef = React.useRef(new Set());
const processingStatusRef = React.useRef({});
const processingPollsRef = React.useRef({});
const processedCleanupRef = React.useRef({});
const hasAccess = currentUserHasAccess();
const baseRepoURL = dropVersion(repo?.version_url || repo?.url || "");
const searchParams = React.useMemo(
Expand All @@ -268,6 +285,76 @@ const CollectionVersionsTab = ({
searchParams.get("expansion_url") ||
searchParams.get("expansion") ||
searchParams.get("expansion_id");

React.useEffect(() => {
processingStatusRef.current = processingStatusByExpansion;
}, [processingStatusByExpansion]);

const clearProcessingPoll = React.useCallback(url => {
if (!processingPollsRef.current[url]) return;
window.clearInterval(processingPollsRef.current[url]);
delete processingPollsRef.current[url];
}, []);

const clearProcessedCleanup = React.useCallback(url => {
if (!processedCleanupRef.current[url]) return;
window.clearTimeout(processedCleanupRef.current[url]);
delete processedCleanupRef.current[url];
}, []);

const markExpansionProcessing = React.useCallback(
url => {
if (!url) return;

clearProcessedCleanup(url);
setProcessingStatusByExpansion(prev => {
if (prev[url]?.state === "processing") return prev;
return {
...prev,
[url]: { state: "processing" }
};
});
},
[clearProcessedCleanup]
);

const markExpansionProcessed = React.useCallback(
(url, onlyIfTracked = false) => {
if (!url) return;

const currentState = processingStatusRef.current[url]?.state;
if (onlyIfTracked && currentState !== "processing") {
clearProcessingPoll(url);
return;
}

clearProcessingPoll(url);
clearProcessedCleanup(url);
setProcessingStatusByExpansion(prev => ({
...prev,
[url]: { state: "processed", processedAt: Date.now() }
}));
processedCleanupRef.current[url] = window.setTimeout(() => {
setProcessingStatusByExpansion(prev => {
if (!prev[url]) return prev;
const next = { ...prev };
delete next[url];
return next;
});
delete processedCleanupRef.current[url];
}, PROCESSED_CHIP_FADE_MS);
},
[clearProcessedCleanup, clearProcessingPoll]
);

React.useEffect(
() => () => {
Object.keys(processingPollsRef.current).forEach(clearProcessingPoll);
Object.keys(processedCleanupRef.current).forEach(clearProcessedCleanup);
},
[clearProcessedCleanup, clearProcessingPoll]
);

React.useEffect(() => {
if (!baseRepoURL || isHeadVersion(repo)) {
setHeadVersion(repo);
Expand Down Expand Up @@ -474,9 +561,80 @@ const CollectionVersionsTab = ({
return versionExpansions.filter(isStaleExpansion).length;
};

const refreshSelectedExpansions = version => {
if (version) fetchExpansions(version, true);
};
const refreshSelectedExpansions = React.useCallback(
version => {
if (version) fetchExpansions(version, true);
},
[fetchExpansions]
);

const pollExpansionProcessing = React.useCallback(
(version, expansion) => {
if (!expansion?.url) return;

APIService.new()
.overrideURL(expansion.url + "processing/")
.get(null, null, null, true)
.then(response => {
if (response?.status !== 200) return;

const isProcessing = parseProcessingValue(response?.data);
if (isProcessing) {
markExpansionProcessing(expansion.url);
return;
}

markExpansionProcessed(expansion.url, true);
refreshSelectedExpansions(version);
});
},
[markExpansionProcessed, markExpansionProcessing, refreshSelectedExpansions]
);

const ensureProcessingPoll = React.useCallback(
(version, expansion) => {
if (!expansion?.url || processingPollsRef.current[expansion.url]) return;

processingPollsRef.current[expansion.url] = window.setInterval(() => {
pollExpansionProcessing(version, expansion);
}, PROCESSING_POLL_INTERVAL_MS);
},
[pollExpansionProcessing]
);

React.useEffect(() => {
const activeProcessingUrls = new Set();

Object.entries(expansionsByVersion).forEach(([versionKey, versionExpansions]) => {
const version = find(displayVersions, item => getVersionKey(item) === versionKey);
if (!version) return;

versionExpansions.forEach(expansion => {
if (!expansion?.url) return;

if (isExpansionProcessing(expansion)) {
activeProcessingUrls.add(expansion.url);
markExpansionProcessing(expansion.url);
ensureProcessingPoll(version, expansion);
return;
}

clearProcessingPoll(expansion.url);
markExpansionProcessed(expansion.url, true);
});
});

Object.keys(processingPollsRef.current).forEach(url => {
if (!activeProcessingUrls.has(url)) clearProcessingPoll(url);
});
}, [
clearProcessingPoll,
displayVersions,
ensureProcessingPoll,
expansionsByVersion,
markExpansionProcessed,
markExpansionProcessing
]);

const onMarkExpansionDefault = (version, expansion) => {
APIService.new()
Expand Down Expand Up @@ -799,6 +957,13 @@ const CollectionVersionsTab = ({
versionExpansions.map(expansion => {
const highlighted =
highlightedExpansion?.url === expansion.url;
const processingStatus =
processingStatusByExpansion[expansion.url];
const processingState =
processingStatus?.state ||
(isExpansionProcessing(expansion)
? "processing"
: null);
const explicitRepoVersions = getExplicitRepoVersions(
expansion
);
Expand Down Expand Up @@ -863,6 +1028,13 @@ const CollectionVersionsTab = ({
icon={<WarningIcon />}
/>
)}
{processingState && (
<ProcessingChip
processed={processingState === "processed"}
fading={processingState === "processed"}
fadeDurationMs={PROCESSED_CHIP_FADE_MS}
/>
)}
</Stack>
{expansion.canonical_url && (
<Typography
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
"none": "None",
"default": "Default",
"processing": "Processing",
"processed": "Processed",
"load_more": "Load more",
"generate_with_ai": "Generate with AI",
"release": "Release",
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/es/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@
"could_not_load_changelog": "No se pudo cargar el registro de cambios.",
"large_sources_take_a_while": "Las fuentes grandes pueden tardar un poco en procesarse.",
"copied_version_url": "URL de versión copiada.",
"processing": "Procesando",
"processed": "Procesado",
"expansions": "Expansiones",
"expansion_dropdown_label": "Expansión: {{mnemonic}}"
},
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/locales/zh/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@
"could_not_load_changelog": "Could not load changelog.",
"large_sources_take_a_while": "Large sources can take a while to process.",
"copied_version_url": "Copied version URL.",
"processing": "处理中",
"processed": "已处理",
"expansions": "扩展",
"expansion_dropdown_label": "扩展:{{mnemonic}}",
"delete_repo_version": "Delete Repo Version",
Expand Down