Line data Source code
1 : /*
2 : * Copyright (C) 2019 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 19 : import React from "react";
7 :
8 : import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip/index.js";
9 : import { BundleIcon } from "@patternfly/react-icons";
10 : import { ListingTable } from "cockpit-components-table.jsx";
11 : import type { History as PackageKitEntry } from "_internal/packagemanager-abstract";
12 : import * as timeformat from "timeformat";
13 :
14 19 : import cockpit from "cockpit";
15 :
16 19 : const _ = cockpit.gettext;
17 :
18 : type Packages = PackageKitEntry["packages"];
19 :
20 8 : function formatPkgs(pkgs: Packages) {
21 8 : const names = Object.keys(pkgs);
22 8 : names.sort();
23 8 : return names.map(n => (
24 8 : <li key={n}>
25 8 : <Tooltip content={ n + " " + pkgs[n] }>
26 8 : <span>{n}</span>
27 8 : </Tooltip>
28 8 : </li>)
29 8 : );
30 8 : }
31 :
32 1 : export const PackageList = ({ packages }: { packages?: Packages }) => packages ? <ul className='flow-list'>{formatPkgs(packages)}</ul> : null;
33 :
34 : interface HistoryProps {
35 : packagekit: PackageKitEntry[];
36 : }
37 :
38 19 : export class History extends React.Component<HistoryProps> {
39 : /* Some PackageKit transactions come in pairs with identical package list,
40 : * but different versions. This is an internal technicality, merge them
41 : * together for presentation.
42 : *
43 : * Returns a time sorted (descending) list of objects like
44 : * { time: timestamp, num_packages: 2, packages: {names...}}
45 : */
46 6 : mergeHistory() {
47 6 : const history: { time: number; packages: Packages; num_packages: number }[] = [];
48 6 : let prevTime: number | undefined;
49 6 : let prevPackages: string[] | undefined;
50 :
51 6 : for (let i = 0; i < this.props.packagekit.length; ++i) {
52 6 : const packages = Object.keys(this.props.packagekit[i].packages);
53 6 : const time = this.props.packagekit[i].timestamp;
54 6 : packages.sort();
55 :
56 2 : if (prevTime && (time - prevTime) <= 600000 /* 10 mins */ &&
57 2 : prevPackages && packages &&
58 2 : prevPackages.toString() == packages.toString())
59 1 : history.pop();
60 :
61 6 : history.push({ time, packages: this.props.packagekit[i].packages, num_packages: packages.length });
62 :
63 6 : if (history.length === 3)
64 6 : break;
65 :
66 6 : prevPackages = packages;
67 6 : prevTime = time;
68 6 : }
69 :
70 6 : return history;
71 6 : }
72 :
73 6 : render() {
74 6 : const history = this.mergeHistory();
75 6 : if (history.length === 0)
76 1 : return null;
77 :
78 6 : const rows = history.map((update, index) => {
79 6 : const pkgcount = (
80 6 : <div className="list-view-pf-additional-info-item">
81 6 : <BundleIcon />
82 6 : { cockpit.format(cockpit.ngettext("$0 package", "$0 packages", update.num_packages), update.num_packages) }
83 6 : </div>);
84 :
85 6 : const expandedContent = <PackageList packages={update.packages} />;
86 :
87 6 : return ({
88 6 : props: { key: update.time },
89 6 : columns: [
90 6 : { title: timeformat.dateTime(update.time), props: { className: "history-time" } },
91 6 : { title: pkgcount, props: { className: "history-pkgcount" } },
92 6 : ],
93 6 : initiallyExpanded: index == 0,
94 6 : hasPadding: true,
95 6 : expandedContent
96 6 : });
97 6 : });
98 :
99 6 : return (
100 6 : <ListingTable aria-label={_("Updates history")}
101 6 : showHeader={false}
102 6 : className="updates-history"
103 6 : columns={[_("Time"), _("History package count")]}
104 6 : rows={rows} />
105 : );
106 6 : }
107 19 : }
|