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

View File

@@ -9,6 +9,7 @@ import {
mutateUninstallScraperPackages,
mutateInstallScraperPackages,
scraperMutationImpactedQueries,
isLoading,
} from "src/core/StashService";
import { useMonitorJob } from "src/utils/job";
import {
@@ -25,9 +26,11 @@ export const InstalledScraperPackages: React.FC = () => {
const [jobID, setJobID] = useState<string>();
const { job } = useMonitorJob(jobID, () => onPackageChanges());
const { data, previousData, refetch, loading, error } =
const { data, previousData, refetch, networkStatus, error } =
useInstalledScraperPackages(loadUpgrades);
const loading = isLoading(networkStatus);
async function onUpdatePackages(packages: GQL.PackageSpecInput[]) {
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">;
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<{
loading?: boolean;
pkg: InstalledPackage;
@@ -75,11 +83,7 @@ const InstalledPackageRow: React.FC<{
const updateAvailable = useMemo(() => {
if (!updatesLoaded) return false;
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;
return hasUpgrade(pkg);
}, [updatesLoaded, pkg]);
return (
@@ -124,6 +128,7 @@ const InstalledPackagesList: React.FC<{
packages: InstalledPackage[];
checkedPackages: InstalledPackage[];
setCheckedPackages: React.Dispatch<React.SetStateAction<InstalledPackage[]>>;
upgradableOnly: boolean;
}> = ({
filter,
packages,
@@ -132,6 +137,7 @@ const InstalledPackagesList: React.FC<{
updatesLoaded,
loading,
error,
upgradableOnly,
}) => {
const checkedMap = useMemo(() => {
const map: Record<string, boolean> = {};
@@ -146,8 +152,10 @@ const InstalledPackagesList: React.FC<{
}, [checkedPackages, packages]);
const filteredPackages = useMemo(() => {
return filterPackages(packages, filter);
}, [filter, packages]);
return filterPackages(packages, filter).filter((pkg) => {
return !updatesLoaded || !upgradableOnly || hasUpgrade(pkg);
});
}, [packages, filter, updatesLoaded, upgradableOnly]);
function toggleAllChecked() {
setCheckedPackages(allChecked ? [] : packages.slice());
@@ -179,10 +187,13 @@ const InstalledPackagesList: React.FC<{
}
if (filteredPackages.length === 0) {
const id = upgradableOnly
? "package_manager.no_upgradable"
: "package_manager.no_packages";
return (
<tr className="package-manager-no-results">
<td colSpan={1000}>
<FormattedMessage id="package_manager.no_packages" />
<FormattedMessage id={id} />
</td>
</tr>
);
@@ -242,6 +253,9 @@ const InstalledPackagesToolbar: React.FC<{
onCheckForUpdates: () => void;
onUpdatePackages: () => void;
onUninstallPackages: () => void;
upgradableOnly: boolean;
setUpgradableOnly: (v: boolean) => void;
}> = ({
loading,
checkedPackages,
@@ -250,8 +264,11 @@ const InstalledPackagesToolbar: React.FC<{
onUninstallPackages,
filter,
setFilter,
upgradableOnly,
setUpgradableOnly,
}) => {
const intl = useIntl();
return (
<div className="package-manager-toolbar">
<ClearableInput
@@ -259,6 +276,15 @@ const InstalledPackagesToolbar: React.FC<{
value={filter}
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" />
<Button
variant="primary"
@@ -306,11 +332,28 @@ export const InstalledPackages: React.FC<{
[]
);
const [filter, setFilter] = useState("");
const [upgradableOnly, setUpgradableOnly] = useState(true);
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(() => {
return filterPackages(checkedPackages, filter);
}, [checkedPackages, filter]);
return filterPackages(checkedPackages, filter).filter((pkg) => {
return !updatesLoaded || !upgradableOnly || hasUpgrade(pkg);
});
}, [checkedPackages, filter, updatesLoaded, upgradableOnly]);
useEffect(() => {
setCheckedPackages((prev) => {
@@ -330,6 +373,12 @@ export const InstalledPackages: React.FC<{
setUninstalling(false);
}
function checkForUpdates() {
// reset to only show upgradable packages
setUpgradableOnly(true);
onCheckForUpdates();
}
return (
<>
<AlertModal
@@ -349,19 +398,22 @@ export const InstalledPackages: React.FC<{
setFilter={(f) => setFilter(f)}
loading={loading}
checkedPackages={filteredPackages}
onCheckForUpdates={onCheckForUpdates}
onCheckForUpdates={() => checkForUpdates()}
onUpdatePackages={() => onUpdatePackages(filteredPackages)}
onUninstallPackages={() => setUninstalling(true)}
upgradableOnly={updatesLoaded && upgradableOnly}
setUpgradableOnly={(v) => setUpgradableOnly(v)}
/>
<InstalledPackagesList
filter={filter}
loading={loading}
error={error}
packages={packages}
packages={sortedPackages}
// use original checked packages so that check boxes are not affected by filter
checkedPackages={checkedPackages}
setCheckedPackages={setCheckedPackages}
updatesLoaded={updatesLoaded}
upgradableOnly={upgradableOnly}
/>
</div>
</>

View File

@@ -2,6 +2,7 @@ import {
ApolloCache,
DocumentNode,
FetchResult,
NetworkStatus,
useQuery,
} from "@apollo/client";
import { Modifiers } from "@apollo/client/cache";
@@ -93,6 +94,16 @@ function deleteObject(
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
export const useFindScene = (id: string) => {

View File

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