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 9 : function formatPkgs(pkgs: Packages) {
21 9 : const names = Object.keys(pkgs);
22 9 : names.sort();
23 9 : return names.map(n => (
24 9 : <li key={n}>
25 9 : <Tooltip content={ n + " " + pkgs[n] }>
26 9 : <span>{n}</span>
27 9 : </Tooltip>
28 9 : </li>)
29 9 : );
30 9 : }
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 7 : mergeHistory() {
47 7 : const history: { time: number; packages: Packages; num_packages: number }[] = [];
48 7 : let prevTime: number | undefined;
49 7 : let prevPackages: string[] | undefined;
50 :
51 7 : for (let i = 0; i < this.props.packagekit.length; ++i) {
52 7 : const packages = Object.keys(this.props.packagekit[i].packages);
53 7 : const time = this.props.packagekit[i].timestamp;
54 7 : 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 7 : history.push({ time, packages: this.props.packagekit[i].packages, num_packages: packages.length });
62 :
63 7 : if (history.length === 3)
64 7 : break;
65 :
66 7 : prevPackages = packages;
67 7 : prevTime = time;
68 7 : }
69 :
70 7 : return history;
71 7 : }
72 :
73 7 : render() {
74 7 : const history = this.mergeHistory();
75 7 : if (history.length === 0)
76 1 : return null;
77 :
78 7 : const rows = history.map((update, index) => {
79 7 : const pkgcount = (
80 7 : <div className="list-view-pf-additional-info-item">
81 7 : <BundleIcon />
82 7 : { cockpit.format(cockpit.ngettext("$0 package", "$0 packages", update.num_packages), update.num_packages) }
83 7 : </div>);
84 :
85 7 : const expandedContent = <PackageList packages={update.packages} />;
86 :
87 7 : return ({
88 7 : props: { key: update.time },
89 7 : columns: [
90 7 : { title: timeformat.dateTime(update.time), props: { className: "history-time" } },
91 7 : { title: pkgcount, props: { className: "history-pkgcount" } },
92 7 : ],
93 7 : initiallyExpanded: index == 0,
94 7 : hasPadding: true,
95 7 : expandedContent
96 7 : });
97 7 : });
98 :
99 7 : return (
100 7 : <ListingTable aria-label={_("Updates history")}
101 7 : showHeader={false}
102 7 : className="updates-history"
103 7 : columns={[_("Time"), _("History package count")]}
104 7 : rows={rows} />
105 : );
106 7 : }
107 19 : }
|