Line data Source code
1 : /*
2 : * Copyright (C) 2019 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 1 : 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 1 : import cockpit from "cockpit";
15 :
16 1 : const _ = cockpit.gettext;
17 :
18 : type Packages = PackageKitEntry["packages"];
19 :
20 1 : function formatPkgs(pkgs: Packages) {
21 1 : const names = Object.keys(pkgs);
22 1 : names.sort();
23 1 : return names.map(n => (
24 1 : <li key={n}>
25 1 : <Tooltip content={ n + " " + pkgs[n] }>
26 1 : <span>{n}</span>
27 1 : </Tooltip>
28 1 : </li>)
29 1 : );
30 1 : }
31 :
32 0 : 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 1 : 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 1 : mergeHistory() {
47 1 : const history: { time: number; packages: Packages; num_packages: number }[] = [];
48 1 : let prevTime: number | undefined;
49 1 : let prevPackages: string[] | undefined;
50 :
51 1 : for (let i = 0; i < this.props.packagekit.length; ++i) {
52 1 : const packages = Object.keys(this.props.packagekit[i].packages);
53 1 : const time = this.props.packagekit[i].timestamp;
54 1 : packages.sort();
55 :
56 0 : if (prevTime && (time - prevTime) <= 600000 /* 10 mins */ &&
57 0 : prevPackages && packages &&
58 0 : prevPackages.toString() == packages.toString())
59 0 : history.pop();
60 :
61 1 : history.push({ time, packages: this.props.packagekit[i].packages, num_packages: packages.length });
62 :
63 1 : if (history.length === 3)
64 1 : break;
65 :
66 1 : prevPackages = packages;
67 1 : prevTime = time;
68 1 : }
69 :
70 1 : return history;
71 1 : }
72 :
73 1 : render() {
74 1 : const history = this.mergeHistory();
75 1 : if (history.length === 0)
76 0 : return null;
77 :
78 1 : const rows = history.map((update, index) => {
79 1 : const pkgcount = (
80 1 : <div className="list-view-pf-additional-info-item">
81 1 : <BundleIcon />
82 1 : { cockpit.format(cockpit.ngettext("$0 package", "$0 packages", update.num_packages), update.num_packages) }
83 1 : </div>);
84 :
85 1 : const expandedContent = <PackageList packages={update.packages} />;
86 :
87 1 : return ({
88 1 : props: { key: update.time },
89 1 : columns: [
90 1 : { title: timeformat.dateTime(update.time), props: { className: "history-time" } },
91 1 : { title: pkgcount, props: { className: "history-pkgcount" } },
92 1 : ],
93 1 : initiallyExpanded: index == 0,
94 1 : hasPadding: true,
95 1 : expandedContent
96 1 : });
97 1 : });
98 :
99 1 : return (
100 1 : <ListingTable aria-label={_("Updates history")}
101 1 : showHeader={false}
102 1 : className="updates-history"
103 1 : columns={[_("Time"), _("History package count")]}
104 1 : rows={rows} />
105 : );
106 1 : }
107 1 : }
|