mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Sort pod names by convering sub-parts (#3314)
This commit is contained in:
parent
e3e7c620a1
commit
64175b24e0
44
src/common/utils/array.ts
Normal file
44
src/common/utils/array.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Tuple<T, N extends number> = N extends N ? number extends N ? T[] : _TupleOf<T, N, []> : never;
|
||||||
|
type _TupleOf<T, N extends number, R extends unknown[]> = R["length"] extends N ? R : _TupleOf<T, N, [T, ...R]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param sources The source arrays
|
||||||
|
* @yields A tuple of the next element from each of the sources
|
||||||
|
* @returns The tuple of all the sources as soon as at least one of the sources is exausted
|
||||||
|
*/
|
||||||
|
export function* zipStrict<T, N extends number>(...sources: Tuple<T[], N>): Iterator<Tuple<T, N>, Tuple<T[], N>> {
|
||||||
|
const maxSafeLength = sources.reduce((prev, cur) => Math.min(prev, cur.length), Number.POSITIVE_INFINITY);
|
||||||
|
|
||||||
|
if (!isFinite(maxSafeLength)) {
|
||||||
|
// There are no sources, thus just return
|
||||||
|
return [] as Tuple<T[], N>;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < maxSafeLength; i += 1) {
|
||||||
|
yield sources.map(source => source[i]) as Tuple<T, N>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sources.map(source => source.slice(maxSafeLength)) as Tuple<T[], N>;
|
||||||
|
}
|
||||||
@ -60,5 +60,6 @@ export * from "./convertMemory";
|
|||||||
export * from "./convertCpu";
|
export * from "./convertCpu";
|
||||||
|
|
||||||
import * as iter from "./iter";
|
import * as iter from "./iter";
|
||||||
|
import * as array from "./array";
|
||||||
|
|
||||||
export { iter };
|
export { iter, array };
|
||||||
|
|||||||
@ -185,3 +185,19 @@ export function reduce<T, R = T>(src: Iterable<T>, reducer: (acc: R, cur: T) =>
|
|||||||
export function join(src: Iterable<string>, connector = ","): string {
|
export function join(src: Iterable<string>, connector = ","): string {
|
||||||
return reduce(src, (acc, cur) => `${acc}${connector}${cur}`, "");
|
return reduce(src, (acc, cur) => `${acc}${connector}${cur}`, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate through `src` and return `true` if `fn` returns a thruthy value for every yielded value.
|
||||||
|
* Otherwise, return `false`. This function short circuits.
|
||||||
|
* @param src The type to be iterated over
|
||||||
|
* @param fn A function to check each iteration
|
||||||
|
*/
|
||||||
|
export function every<T>(src: Iterable<T>, fn: (val: T) => any): boolean {
|
||||||
|
for (const val of src) {
|
||||||
|
if (!fn(val)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@ -24,16 +24,44 @@ import * as iter from "./iter";
|
|||||||
import type { RawHelmChart } from "../k8s-api/endpoints/helm-charts.api";
|
import type { RawHelmChart } from "../k8s-api/endpoints/helm-charts.api";
|
||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
|
|
||||||
export function sortCompare<T>(left: T, right: T): -1 | 0 | 1 {
|
export enum Ordering {
|
||||||
|
LESS = -1,
|
||||||
|
EQUAL = 0,
|
||||||
|
GREATER = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function switches the direction of `ordering` if `direction` is `"desc"`
|
||||||
|
* @param ordering The original ordering (assumed to be an "asc" ordering)
|
||||||
|
* @param direction The new desired direction
|
||||||
|
*/
|
||||||
|
export function rectifyOrdering(ordering: Ordering, direction: "asc" | "desc"): Ordering {
|
||||||
|
if (direction === "desc") {
|
||||||
|
return -ordering;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordering;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ascending sorting function
|
||||||
|
* @param left An item from an array
|
||||||
|
* @param right An item from an array
|
||||||
|
* @returns The relative ordering in an ascending manner.
|
||||||
|
* - Less if left < right
|
||||||
|
* - Equal if left == right
|
||||||
|
* - Greater if left > right
|
||||||
|
*/
|
||||||
|
export function sortCompare<T>(left: T, right: T): Ordering {
|
||||||
if (left < right) {
|
if (left < right) {
|
||||||
return -1;
|
return Ordering.LESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (left === right) {
|
if (left === right) {
|
||||||
return 0;
|
return Ordering.EQUAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return Ordering.GREATER;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChartVersion {
|
interface ChartVersion {
|
||||||
@ -41,17 +69,17 @@ interface ChartVersion {
|
|||||||
__version?: SemVer;
|
__version?: SemVer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortCompareChartVersions(left: ChartVersion, right: ChartVersion): -1 | 0 | 1 {
|
export function sortCompareChartVersions(left: ChartVersion, right: ChartVersion): Ordering {
|
||||||
if (left.__version && right.__version) {
|
if (left.__version && right.__version) {
|
||||||
return semver.compare(right.__version, left.__version);
|
return semver.compare(right.__version, left.__version);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!left.__version && right.__version) {
|
if (!left.__version && right.__version) {
|
||||||
return 1;
|
return Ordering.GREATER;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (left.__version && !right.__version) {
|
if (left.__version && !right.__version) {
|
||||||
return -1;
|
return Ordering.LESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortCompare(left.version, right.version);
|
return sortCompare(left.version, right.version);
|
||||||
|
|||||||
@ -71,11 +71,11 @@ export class DeploymentReplicaSets extends React.Component<Props> {
|
|||||||
<DrawerTitle title="Deploy Revisions"/>
|
<DrawerTitle title="Deploy Revisions"/>
|
||||||
<Table
|
<Table
|
||||||
selectable
|
selectable
|
||||||
|
tableId="deployment_replica_sets_view"
|
||||||
scrollable={false}
|
scrollable={false}
|
||||||
sortable={this.sortingCallbacks}
|
sortable={this.sortingCallbacks}
|
||||||
sortByDefault={{ sortBy: sortBy.pods, orderBy: "desc" }}
|
sortByDefault={{ sortBy: sortBy.pods, orderBy: "desc" }}
|
||||||
sortSyncWithUrl={false}
|
sortSyncWithUrl={false}
|
||||||
tableId="deployment_replica_sets_view"
|
|
||||||
className="box grow"
|
className="box grow"
|
||||||
>
|
>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import { eventStore } from "../+events/event.store";
|
|||||||
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
import { KubeObjectListLayout } from "../kube-object-list-layout";
|
||||||
import { nodesApi, Pod } from "../../../common/k8s-api/endpoints";
|
import { nodesApi, Pod } from "../../../common/k8s-api/endpoints";
|
||||||
import { StatusBrick } from "../status-brick";
|
import { StatusBrick } from "../status-brick";
|
||||||
import { cssNames, stopPropagation } from "../../utils";
|
import { cssNames, getConvertedParts, stopPropagation } from "../../utils";
|
||||||
import toPairs from "lodash/toPairs";
|
import toPairs from "lodash/toPairs";
|
||||||
import startCase from "lodash/startCase";
|
import startCase from "lodash/startCase";
|
||||||
import kebabCase from "lodash/kebabCase";
|
import kebabCase from "lodash/kebabCase";
|
||||||
@ -98,7 +98,7 @@ export class Pods extends React.Component<Props> {
|
|||||||
tableId = "workloads_pods"
|
tableId = "workloads_pods"
|
||||||
isConfigurable
|
isConfigurable
|
||||||
sortingCallbacks={{
|
sortingCallbacks={{
|
||||||
[columnId.name]: pod => pod.getName(),
|
[columnId.name]: pod => getConvertedParts(pod.getName()),
|
||||||
[columnId.namespace]: pod => pod.getNs(),
|
[columnId.namespace]: pod => pod.getNs(),
|
||||||
[columnId.containers]: pod => pod.getContainers().length,
|
[columnId.containers]: pod => pod.getContainers().length,
|
||||||
[columnId.restarts]: pod => pod.getRestartsCount(),
|
[columnId.restarts]: pod => pod.getRestartsCount(),
|
||||||
|
|||||||
155
src/renderer/components/table/__tests__/getSorted.test.ts
Normal file
155
src/renderer/components/table/__tests__/getSorted.test.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { getSorted } from "../sorting";
|
||||||
|
|
||||||
|
describe("Table tests", () => {
|
||||||
|
describe("getSorted", () => {
|
||||||
|
it.each([undefined, 5, "", true, {}, []])("should not sort since %j is not a function", () => {
|
||||||
|
expect(getSorted([1, 2, 4, 3], undefined, "asc")).toStrictEqual([1, 2, 4, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should sort numerically asc and not touch the original list", () => {
|
||||||
|
const i = [1, 2, 4, 3];
|
||||||
|
|
||||||
|
expect(getSorted(i, v => v, "asc")).toStrictEqual([1, 2, 3, 4]);
|
||||||
|
expect(i).toStrictEqual([1, 2, 4, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should sort numerically desc and not touch the original list", () => {
|
||||||
|
const i = [1, 2, 4, 3];
|
||||||
|
|
||||||
|
expect(getSorted(i, v => v, "desc")).toStrictEqual([4, 3, 2, 1]);
|
||||||
|
expect(i).toStrictEqual([1, 2, 4, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should sort numerically asc (by defaul) and not touch the original list", () => {
|
||||||
|
const i = [1, 2, 4, 3];
|
||||||
|
|
||||||
|
expect(getSorted(i, v => v, "foobar")).toStrictEqual([1, 2, 3, 4]);
|
||||||
|
expect(i).toStrictEqual([1, 2, 4, 3]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("multi-part", () => {
|
||||||
|
it("should sort each part by its order", () => {
|
||||||
|
const i = ["a", "c", "b.1", "b.2", "d"];
|
||||||
|
|
||||||
|
expect(getSorted(i, v => v.split("."), "desc")).toStrictEqual(["d", "c", "b.2", "b.1", "a"]);
|
||||||
|
expect(i).toStrictEqual(["a", "c", "b.1", "b.2", "d"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be a stable sort", () => {
|
||||||
|
const i = [{
|
||||||
|
val: "a",
|
||||||
|
k: 1,
|
||||||
|
}, {
|
||||||
|
val: "c",
|
||||||
|
k: 2
|
||||||
|
}, {
|
||||||
|
val: "b.1",
|
||||||
|
k: 3
|
||||||
|
}, {
|
||||||
|
val: "b.2",
|
||||||
|
k: 4
|
||||||
|
}, {
|
||||||
|
val: "d",
|
||||||
|
k: 5
|
||||||
|
}, {
|
||||||
|
val: "b.2",
|
||||||
|
k: -10
|
||||||
|
}];
|
||||||
|
const dup = cloneDeep(i);
|
||||||
|
const expected = [
|
||||||
|
{
|
||||||
|
val: "a",
|
||||||
|
k: 1,
|
||||||
|
}, {
|
||||||
|
val: "b.1",
|
||||||
|
k: 3
|
||||||
|
}, {
|
||||||
|
val: "b.2",
|
||||||
|
k: 4
|
||||||
|
}, {
|
||||||
|
val: "b.2",
|
||||||
|
k: -10
|
||||||
|
}, {
|
||||||
|
val: "c",
|
||||||
|
k: 2
|
||||||
|
}, {
|
||||||
|
val: "d",
|
||||||
|
k: 5
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(getSorted(i, ({ val }) => val.split("."), "asc")).toStrictEqual(expected);
|
||||||
|
expect(i).toStrictEqual(dup);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be a stable sort #2", () => {
|
||||||
|
const i = [{
|
||||||
|
val: "a",
|
||||||
|
k: 1,
|
||||||
|
}, {
|
||||||
|
val: "b.2",
|
||||||
|
k: -10
|
||||||
|
}, {
|
||||||
|
val: "c",
|
||||||
|
k: 2
|
||||||
|
}, {
|
||||||
|
val: "b.1",
|
||||||
|
k: 3
|
||||||
|
}, {
|
||||||
|
val: "b.2",
|
||||||
|
k: 4
|
||||||
|
}, {
|
||||||
|
val: "d",
|
||||||
|
k: 5
|
||||||
|
}];
|
||||||
|
const dup = cloneDeep(i);
|
||||||
|
const expected = [
|
||||||
|
{
|
||||||
|
val: "a",
|
||||||
|
k: 1,
|
||||||
|
}, {
|
||||||
|
val: "b.1",
|
||||||
|
k: 3
|
||||||
|
}, {
|
||||||
|
val: "b.2",
|
||||||
|
k: -10
|
||||||
|
}, {
|
||||||
|
val: "b.2",
|
||||||
|
k: 4
|
||||||
|
}, {
|
||||||
|
val: "c",
|
||||||
|
k: 2
|
||||||
|
}, {
|
||||||
|
val: "d",
|
||||||
|
k: 5
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(getSorted(i, ({ val }) => val.split("."), "asc")).toStrictEqual(expected);
|
||||||
|
expect(i).toStrictEqual(dup);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
68
src/renderer/components/table/sorting.ts
Normal file
68
src/renderer/components/table/sorting.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { TableSortCallback } from "./table";
|
||||||
|
import { array, Ordering, rectifyOrdering, sortCompare } from "../../utils";
|
||||||
|
|
||||||
|
export function getSorted<T>(rawItems: T[], sortingCallback: TableSortCallback<T> | undefined, orderByRaw: string): T[] {
|
||||||
|
if (typeof sortingCallback !== "function") {
|
||||||
|
return rawItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderBy = orderByRaw === "asc" || orderByRaw === "desc" ? orderByRaw : "asc";
|
||||||
|
const sortData = rawItems.map((item, index) => ({
|
||||||
|
index,
|
||||||
|
sortBy: sortingCallback(item),
|
||||||
|
}));
|
||||||
|
|
||||||
|
sortData.sort((left, right) => {
|
||||||
|
if (!Array.isArray(left.sortBy) && !Array.isArray(right.sortBy)) {
|
||||||
|
return rectifyOrdering(sortCompare(left.sortBy, right.sortBy), orderBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftSortBy = [left.sortBy].flat();
|
||||||
|
const rightSortBy = [right.sortBy].flat();
|
||||||
|
const zipIter = array.zipStrict(leftSortBy, rightSortBy);
|
||||||
|
let r = zipIter.next();
|
||||||
|
|
||||||
|
for (; r.done === false; r = zipIter.next()) {
|
||||||
|
const [nextL, nextR] = r.value;
|
||||||
|
|
||||||
|
const sortOrder = rectifyOrdering(sortCompare(nextL, nextR), orderBy);
|
||||||
|
|
||||||
|
if (sortOrder !== Ordering.EQUAL) {
|
||||||
|
return sortOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [leftRest, rightRest] = r.value;
|
||||||
|
|
||||||
|
return leftRest.length - rightRest.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = [];
|
||||||
|
|
||||||
|
for (const { index } of sortData) {
|
||||||
|
res.push(rawItems[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
@ -22,9 +22,8 @@
|
|||||||
import "./table.scss";
|
import "./table.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { orderBy } from "lodash";
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { boundMethod, cssNames, noop } from "../../utils";
|
import { boundMethod, cssNames } from "../../utils";
|
||||||
import { TableRow, TableRowElem, TableRowProps } from "./table-row";
|
import { TableRow, TableRowElem, TableRowProps } from "./table-row";
|
||||||
import { TableHead, TableHeadElem, TableHeadProps } from "./table-head";
|
import { TableHead, TableHeadElem, TableHeadProps } from "./table-head";
|
||||||
import type { TableCellElem } from "./table-cell";
|
import type { TableCellElem } from "./table-cell";
|
||||||
@ -32,6 +31,7 @@ import { VirtualList } from "../virtual-list";
|
|||||||
import { createPageParam } from "../../navigation";
|
import { createPageParam } from "../../navigation";
|
||||||
import { getSortParams, setSortParams } from "./table.storage";
|
import { getSortParams, setSortParams } from "./table.storage";
|
||||||
import { computed, makeObservable } from "mobx";
|
import { computed, makeObservable } from "mobx";
|
||||||
|
import { getSorted } from "./sorting";
|
||||||
|
|
||||||
export type TableSortBy = string;
|
export type TableSortBy = string;
|
||||||
export type TableOrderBy = "asc" | "desc" | string;
|
export type TableOrderBy = "asc" | "desc" | string;
|
||||||
@ -92,16 +92,22 @@ export class Table<Item> extends React.Component<TableProps<Item>> {
|
|||||||
const { sortable, tableId } = this.props;
|
const { sortable, tableId } = this.props;
|
||||||
|
|
||||||
if (sortable && !tableId) {
|
if (sortable && !tableId) {
|
||||||
console.error("[Table]: sorted table requires props.tableId to be specified");
|
console.error("Table must have props.tableId if props.sortable is specified");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed get isSortable() {
|
||||||
|
const { sortable, tableId } = this.props;
|
||||||
|
|
||||||
|
return Boolean(sortable && tableId);
|
||||||
|
}
|
||||||
|
|
||||||
@computed get sortParams() {
|
@computed get sortParams() {
|
||||||
return Object.assign({}, this.props.sortByDefault, getSortParams(this.props.tableId));
|
return Object.assign({}, this.props.sortByDefault, getSortParams(this.props.tableId));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHead() {
|
renderHead() {
|
||||||
const { sortable, children } = this.props;
|
const { children } = this.props;
|
||||||
const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[];
|
const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[];
|
||||||
const headElem: React.ReactElement<TableHeadProps> = content.find(elem => elem.type === TableHead);
|
const headElem: React.ReactElement<TableHeadProps> = content.find(elem => elem.type === TableHead);
|
||||||
|
|
||||||
@ -109,7 +115,7 @@ export class Table<Item> extends React.Component<TableProps<Item>> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortable) {
|
if (this.isSortable) {
|
||||||
const columns = React.Children.toArray(headElem.props.children) as TableCellElem[];
|
const columns = React.Children.toArray(headElem.props.children) as TableCellElem[];
|
||||||
|
|
||||||
return React.cloneElement(headElem, {
|
return React.cloneElement(headElem, {
|
||||||
@ -136,14 +142,12 @@ export class Table<Item> extends React.Component<TableProps<Item>> {
|
|||||||
return headElem;
|
return headElem;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSorted(items: any[]) {
|
getSorted(rawItems: Item[]) {
|
||||||
const { sortBy, orderBy: order } = this.sortParams;
|
const { sortBy, orderBy: orderByRaw } = this.sortParams;
|
||||||
const sortingCallback = this.props.sortable[sortBy] || noop;
|
|
||||||
|
|
||||||
return orderBy(items, sortingCallback, order as any);
|
return getSorted(rawItems, this.props.sortable[sortBy], orderByRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
|
||||||
protected onSort({ sortBy, orderBy }: TableSortParams) {
|
protected onSort({ sortBy, orderBy }: TableSortParams) {
|
||||||
setSortParams(this.props.tableId, { sortBy, orderBy });
|
setSortParams(this.props.tableId, { sortBy, orderBy });
|
||||||
const { sortSyncWithUrl, onSort } = this.props;
|
const { sortSyncWithUrl, onSort } = this.props;
|
||||||
@ -153,9 +157,7 @@ export class Table<Item> extends React.Component<TableProps<Item>> {
|
|||||||
orderByUrlParam.set(orderBy);
|
orderByUrlParam.set(orderBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onSort) {
|
onSort?.({ sortBy, orderBy });
|
||||||
onSort({ sortBy, orderBy });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@boundMethod
|
@boundMethod
|
||||||
@ -183,12 +185,12 @@ export class Table<Item> extends React.Component<TableProps<Item>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderRows() {
|
renderRows() {
|
||||||
const { sortable, noItems, virtual, customRowHeights, rowLineHeight, rowPadding, items, getTableRow, selectedItemId, className } = this.props;
|
const { noItems, virtual, customRowHeights, rowLineHeight, rowPadding, items, getTableRow, selectedItemId, className } = this.props;
|
||||||
const content = this.getContent();
|
const content = this.getContent();
|
||||||
let rows: React.ReactElement<TableRowProps>[] = content.filter(elem => elem.type === TableRow);
|
let rows: React.ReactElement<TableRowProps>[] = content.filter(elem => elem.type === TableRow);
|
||||||
let sortedItems = rows.length ? rows.map(row => row.props.sortItem) : [...items];
|
let sortedItems = rows.length ? rows.map(row => row.props.sortItem) : [...items];
|
||||||
|
|
||||||
if (sortable) {
|
if (this.isSortable) {
|
||||||
sortedItems = this.getSorted(sortedItems);
|
sortedItems = this.getSorted(sortedItems);
|
||||||
|
|
||||||
if (rows.length) {
|
if (rows.length) {
|
||||||
@ -228,15 +230,13 @@ export class Table<Item> extends React.Component<TableProps<Item>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { selectable, scrollable, sortable, autoSize, virtual } = this.props;
|
const { selectable, scrollable, autoSize, virtual, className } = this.props;
|
||||||
let { className } = this.props;
|
const classNames = cssNames("Table flex column", className, {
|
||||||
|
selectable, scrollable, sortable: this.isSortable, autoSize, virtual,
|
||||||
className = cssNames("Table flex column", className, {
|
|
||||||
selectable, scrollable, sortable, autoSize, virtual,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={classNames}>
|
||||||
{this.renderHead()}
|
{this.renderHead()}
|
||||||
{this.renderRows()}
|
{this.renderRows()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
36
src/renderer/utils/__tests__/converted-name.test.ts
Normal file
36
src/renderer/utils/__tests__/converted-name.test.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getConvertedParts } from "../name-parts";
|
||||||
|
|
||||||
|
describe("getConvertedParts", () => {
|
||||||
|
it.each([
|
||||||
|
["hello", ["hello"]],
|
||||||
|
["hello.goodbye", ["hello", "goodbye"]],
|
||||||
|
["hello.1", ["hello", 1]],
|
||||||
|
["3-hello.1", [3, "hello", 1]],
|
||||||
|
["3_hello.1", [3, "hello", 1]],
|
||||||
|
["3_hello.1/foobar", [3, "hello", 1, "foobar"]],
|
||||||
|
["3_hello.1/foobar\\new", [3, "hello", 1, "foobar", "new"]],
|
||||||
|
])("Splits '%s' as into %j", (input, output) => {
|
||||||
|
expect(getConvertedParts(input)).toEqual(output);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -22,19 +22,18 @@
|
|||||||
// Common usage utils & helpers
|
// Common usage utils & helpers
|
||||||
|
|
||||||
export * from "../../common/utils";
|
export * from "../../common/utils";
|
||||||
|
|
||||||
export * from "./cssVar";
|
|
||||||
export * from "./cssNames";
|
|
||||||
export * from "../../common/event-emitter";
|
export * from "../../common/event-emitter";
|
||||||
export * from "./saveFile";
|
|
||||||
export * from "./prevDefault";
|
|
||||||
export * from "./storageHelper";
|
|
||||||
export * from "./createStorage";
|
|
||||||
export * from "./interval";
|
|
||||||
export * from "./copyToClipboard";
|
export * from "./copyToClipboard";
|
||||||
export * from "./isReactNode";
|
export * from "./createStorage";
|
||||||
export * from "../../common/utils/convertMemory";
|
export * from "./cssNames";
|
||||||
export * from "../../common/utils/convertCpu";
|
export * from "./cssVar";
|
||||||
export * from "./metricUnitsToNumber";
|
|
||||||
export * from "./display-booleans";
|
export * from "./display-booleans";
|
||||||
|
export * from "./interval";
|
||||||
export * from "./isMiddleClick";
|
export * from "./isMiddleClick";
|
||||||
|
export * from "./isReactNode";
|
||||||
|
export * from "./metricUnitsToNumber";
|
||||||
|
export * from "./name-parts";
|
||||||
|
export * from "./prevDefault";
|
||||||
|
export * from "./saveFile";
|
||||||
|
export * from "./storageHelper";
|
||||||
|
|||||||
36
src/renderer/utils/name-parts.ts
Normal file
36
src/renderer/utils/name-parts.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2021 OpenLens Authors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to
|
||||||
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split `name` into the parts seperated by one or more of (-, _, or .) and
|
||||||
|
* the sections can be converted to numbers will be converted
|
||||||
|
* @param name A kube object name
|
||||||
|
* @returns The converted parts of the name
|
||||||
|
*/
|
||||||
|
export function getConvertedParts(name: string): (string | number)[] {
|
||||||
|
return name
|
||||||
|
.split(/[-_./\\]+/)
|
||||||
|
.map(part => {
|
||||||
|
const converted = +part;
|
||||||
|
|
||||||
|
return isNaN(converted) ? part : converted;
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user