Show upgradable packages only when checking for updates (#4599)

* Sort upgradable packages to top
* Show upgradable packages only by default
* Fix loading state when refetching
This commit is contained in:
WithoutPants
2024-02-21 08:24:18 +11:00
committed by GitHub
parent 76e5598876
commit 5bb9bf902c
5 changed files with 84 additions and 14 deletions

View File

@@ -9,6 +9,7 @@ import {
mutateUninstallPluginPackages, mutateUninstallPluginPackages,
mutateUpdatePluginPackages, mutateUpdatePluginPackages,
pluginMutationImpactedQueries, pluginMutationImpactedQueries,
isLoading,
} from "src/core/StashService"; } from "src/core/StashService";
import { useMonitorJob } from "src/utils/job"; import { useMonitorJob } from "src/utils/job";
import { import {
@@ -25,9 +26,11 @@ export const InstalledPluginPackages: React.FC = () => {
const [jobID, setJobID] = useState<string>(); const [jobID, setJobID] = useState<string>();
const { job } = useMonitorJob(jobID, () => onPackageChanges()); const { job } = useMonitorJob(jobID, () => onPackageChanges());
const { data, previousData, refetch, loading, error } = const { data, previousData, refetch, networkStatus, error } =
useInstalledPluginPackages(loadUpgrades); useInstalledPluginPackages(loadUpgrades);
const loading = isLoading(networkStatus);
async function onUpdatePackages(packages: GQL.PackageSpecInput[]) { async function onUpdatePackages(packages: GQL.PackageSpecInput[]) {
const r = await mutateUpdatePluginPackages(packages); const r = await mutateUpdatePluginPackages(packages);

View File

@@ -9,6 +9,7 @@ import {
mutateUninstallScraperPackages, mutateUninstallScraperPackages,
mutateInstallScraperPackages, mutateInstallScraperPackages,
scraperMutationImpactedQueries, scraperMutationImpactedQueries,
isLoading,
} from "src/core/StashService"; } from "src/core/StashService";
import { useMonitorJob } from "src/utils/job"; import { useMonitorJob } from "src/utils/job";
import { import {
@@ -25,9 +26,11 @@ export const InstalledScraperPackages: React.FC = () => {
const [jobID, setJobID] = useState<string>(); const [jobID, setJobID] = useState<string>();
const { job } = useMonitorJob(jobID, () => onPackageChanges()); const { job } = useMonitorJob(jobID, () => onPackageChanges());
const { data, previousData, refetch, loading, error } = const { data, previousData, refetch, networkStatus, error } =
useInstalledScraperPackages(loadUpgrades); useInstalledScraperPackages(loadUpgrades);
const loading = isLoading(networkStatus);
async function onUpdatePackages(packages: GQL.PackageSpecInput[]) { async function onUpdatePackages(packages: GQL.PackageSpecInput[]) {
const r = await mutateUpdateScraperPackages(packages); const r = await mutateUpdateScraperPackages(packages);

View File

@@ -64,6 +64,14 @@ function filterPackages<T extends IPackage>(packages: T[], filter: string) {
export type InstalledPackage = Omit<GQL.Package, "requires">; export type InstalledPackage = Omit<GQL.Package, "requires">;
function hasUpgrade(pkg: InstalledPackage) {
if (!pkg.date || !pkg.source_package?.date) return false;
const pkgDate = new Date(pkg.date);
const upgradeDate = new Date(pkg.source_package.date);
return upgradeDate > pkgDate;
}
const InstalledPackageRow: React.FC<{ const InstalledPackageRow: React.FC<{
loading?: boolean; loading?: boolean;
pkg: InstalledPackage; pkg: InstalledPackage;
@@ -75,11 +83,7 @@ const InstalledPackageRow: React.FC<{
const updateAvailable = useMemo(() => { const updateAvailable = useMemo(() => {
if (!updatesLoaded) return false; if (!updatesLoaded) return false;
if (!pkg.date || !pkg.source_package?.date) return false; return hasUpgrade(pkg);
const pkgDate = new Date(pkg.date);
const upgradeDate = new Date(pkg.source_package.date);
return upgradeDate > pkgDate;
}, [updatesLoaded, pkg]); }, [updatesLoaded, pkg]);
return ( return (
@@ -124,6 +128,7 @@ const InstalledPackagesList: React.FC<{
packages: InstalledPackage[]; packages: InstalledPackage[];
checkedPackages: InstalledPackage[]; checkedPackages: InstalledPackage[];
setCheckedPackages: React.Dispatch<React.SetStateAction<InstalledPackage[]>>; setCheckedPackages: React.Dispatch<React.SetStateAction<InstalledPackage[]>>;
upgradableOnly: boolean;
}> = ({ }> = ({
filter, filter,
packages, packages,
@@ -132,6 +137,7 @@ const InstalledPackagesList: React.FC<{
updatesLoaded, updatesLoaded,
loading, loading,
error, error,
upgradableOnly,
}) => { }) => {
const checkedMap = useMemo(() => { const checkedMap = useMemo(() => {
const map: Record<string, boolean> = {}; const map: Record<string, boolean> = {};
@@ -146,8 +152,10 @@ const InstalledPackagesList: React.FC<{
}, [checkedPackages, packages]); }, [checkedPackages, packages]);
const filteredPackages = useMemo(() => { const filteredPackages = useMemo(() => {
return filterPackages(packages, filter); return filterPackages(packages, filter).filter((pkg) => {
}, [filter, packages]); return !updatesLoaded || !upgradableOnly || hasUpgrade(pkg);
});
}, [packages, filter, updatesLoaded, upgradableOnly]);
function toggleAllChecked() { function toggleAllChecked() {
setCheckedPackages(allChecked ? [] : packages.slice()); setCheckedPackages(allChecked ? [] : packages.slice());
@@ -179,10 +187,13 @@ const InstalledPackagesList: React.FC<{
} }
if (filteredPackages.length === 0) { if (filteredPackages.length === 0) {
const id = upgradableOnly
? "package_manager.no_upgradable"
: "package_manager.no_packages";
return ( return (
<tr className="package-manager-no-results"> <tr className="package-manager-no-results">
<td colSpan={1000}> <td colSpan={1000}>
<FormattedMessage id="package_manager.no_packages" /> <FormattedMessage id={id} />
</td> </td>
</tr> </tr>
); );
@@ -242,6 +253,9 @@ const InstalledPackagesToolbar: React.FC<{
onCheckForUpdates: () => void; onCheckForUpdates: () => void;
onUpdatePackages: () => void; onUpdatePackages: () => void;
onUninstallPackages: () => void; onUninstallPackages: () => void;
upgradableOnly: boolean;
setUpgradableOnly: (v: boolean) => void;
}> = ({ }> = ({
loading, loading,
checkedPackages, checkedPackages,
@@ -250,8 +264,11 @@ const InstalledPackagesToolbar: React.FC<{
onUninstallPackages, onUninstallPackages,
filter, filter,
setFilter, setFilter,
upgradableOnly,
setUpgradableOnly,
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
return ( return (
<div className="package-manager-toolbar"> <div className="package-manager-toolbar">
<ClearableInput <ClearableInput
@@ -259,6 +276,15 @@ const InstalledPackagesToolbar: React.FC<{
value={filter} value={filter}
setValue={(v) => setFilter(v)} setValue={(v) => setFilter(v)}
/> />
{upgradableOnly && (
<Button
size="sm"
variant="primary"
onClick={() => setUpgradableOnly(!upgradableOnly)}
>
<FormattedMessage id="package_manager.show_all" />
</Button>
)}
<div className="flex-grow-1" /> <div className="flex-grow-1" />
<Button <Button
variant="primary" variant="primary"
@@ -306,11 +332,28 @@ export const InstalledPackages: React.FC<{
[] []
); );
const [filter, setFilter] = useState(""); const [filter, setFilter] = useState("");
const [upgradableOnly, setUpgradableOnly] = useState(true);
const [uninstalling, setUninstalling] = useState(false); const [uninstalling, setUninstalling] = useState(false);
// sort packages so that those with updates are at the top
const sortedPackages = useMemo(() => {
return packages.slice().sort((a, b) => {
const aHasUpdate = hasUpgrade(a);
const bHasUpdate = hasUpgrade(b);
if (aHasUpdate && !bHasUpdate) return -1;
if (!aHasUpdate && bHasUpdate) return 1;
// sort by name
return a.package_id.localeCompare(b.package_id);
});
}, [packages]);
const filteredPackages = useMemo(() => { const filteredPackages = useMemo(() => {
return filterPackages(checkedPackages, filter); return filterPackages(checkedPackages, filter).filter((pkg) => {
}, [checkedPackages, filter]); return !updatesLoaded || !upgradableOnly || hasUpgrade(pkg);
});
}, [checkedPackages, filter, updatesLoaded, upgradableOnly]);
useEffect(() => { useEffect(() => {
setCheckedPackages((prev) => { setCheckedPackages((prev) => {
@@ -330,6 +373,12 @@ export const InstalledPackages: React.FC<{
setUninstalling(false); setUninstalling(false);
} }
function checkForUpdates() {
// reset to only show upgradable packages
setUpgradableOnly(true);
onCheckForUpdates();
}
return ( return (
<> <>
<AlertModal <AlertModal
@@ -349,19 +398,22 @@ export const InstalledPackages: React.FC<{
setFilter={(f) => setFilter(f)} setFilter={(f) => setFilter(f)}
loading={loading} loading={loading}
checkedPackages={filteredPackages} checkedPackages={filteredPackages}
onCheckForUpdates={onCheckForUpdates} onCheckForUpdates={() => checkForUpdates()}
onUpdatePackages={() => onUpdatePackages(filteredPackages)} onUpdatePackages={() => onUpdatePackages(filteredPackages)}
onUninstallPackages={() => setUninstalling(true)} onUninstallPackages={() => setUninstalling(true)}
upgradableOnly={updatesLoaded && upgradableOnly}
setUpgradableOnly={(v) => setUpgradableOnly(v)}
/> />
<InstalledPackagesList <InstalledPackagesList
filter={filter} filter={filter}
loading={loading} loading={loading}
error={error} error={error}
packages={packages} packages={sortedPackages}
// use original checked packages so that check boxes are not affected by filter // use original checked packages so that check boxes are not affected by filter
checkedPackages={checkedPackages} checkedPackages={checkedPackages}
setCheckedPackages={setCheckedPackages} setCheckedPackages={setCheckedPackages}
updatesLoaded={updatesLoaded} updatesLoaded={updatesLoaded}
upgradableOnly={upgradableOnly}
/> />
</div> </div>
</> </>

View File

@@ -2,6 +2,7 @@ import {
ApolloCache, ApolloCache,
DocumentNode, DocumentNode,
FetchResult, FetchResult,
NetworkStatus,
useQuery, useQuery,
} from "@apollo/client"; } from "@apollo/client";
import { Modifiers } from "@apollo/client/cache"; import { Modifiers } from "@apollo/client/cache";
@@ -93,6 +94,16 @@ function deleteObject(
cache.evict({ id: cache.identify(obj) }); cache.evict({ id: cache.identify(obj) });
} }
export function isLoading(networkStatus: NetworkStatus) {
// useQuery hook loading field only returns true when initially loading the query
// and not during subsequent fetches
return (
networkStatus === NetworkStatus.loading ||
networkStatus === NetworkStatus.fetchMore ||
networkStatus === NetworkStatus.refetch
);
}
/// Object queries /// Object queries
export const useFindScene = (id: string) => { export const useFindScene = (id: string) => {

View File

@@ -1095,6 +1095,7 @@
"latest_version": "Latest Version", "latest_version": "Latest Version",
"no_packages": "No packages found", "no_packages": "No packages found",
"no_sources": "No sources configured", "no_sources": "No sources configured",
"no_upgradable": "No upgradable packages found",
"package": "Package", "package": "Package",
"required_by": "Required by {packages}", "required_by": "Required by {packages}",
"selected_only": "Selected only", "selected_only": "Selected only",