From 64175b24e0d82d0bfb49f39003c592c5b638fe05 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 19 Oct 2021 10:36:49 -0400 Subject: [PATCH] Sort pod names by convering sub-parts (#3314) --- src/common/utils/array.ts | 44 +++++ src/common/utils/index.ts | 3 +- src/common/utils/iter.ts | 16 ++ src/common/utils/sort-compare.ts | 42 ++++- .../deployment-replicasets.tsx | 2 +- .../components/+workloads-pods/pods.tsx | 4 +- .../table/__tests__/getSorted.test.ts | 155 ++++++++++++++++++ src/renderer/components/table/sorting.ts | 68 ++++++++ src/renderer/components/table/table.tsx | 42 ++--- .../utils/__tests__/converted-name.test.ts | 36 ++++ src/renderer/utils/index.ts | 23 ++- src/renderer/utils/name-parts.ts | 36 ++++ 12 files changed, 427 insertions(+), 44 deletions(-) create mode 100644 src/common/utils/array.ts create mode 100644 src/renderer/components/table/__tests__/getSorted.test.ts create mode 100644 src/renderer/components/table/sorting.ts create mode 100644 src/renderer/utils/__tests__/converted-name.test.ts create mode 100644 src/renderer/utils/name-parts.ts diff --git a/src/common/utils/array.ts b/src/common/utils/array.ts new file mode 100644 index 0000000000..b0561e3eaf --- /dev/null +++ b/src/common/utils/array.ts @@ -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 = N extends N ? number extends N ? T[] : _TupleOf : never; +type _TupleOf = R["length"] extends N ? R : _TupleOf; + +/** + * + * @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(...sources: Tuple): Iterator, Tuple> { + 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; + } + + for (let i = 0; i < maxSafeLength; i += 1) { + yield sources.map(source => source[i]) as Tuple; + } + + return sources.map(source => source.slice(maxSafeLength)) as Tuple; +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index 6035b9f408..fdf91d39c1 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -60,5 +60,6 @@ export * from "./convertMemory"; export * from "./convertCpu"; import * as iter from "./iter"; +import * as array from "./array"; -export { iter }; +export { iter, array }; diff --git a/src/common/utils/iter.ts b/src/common/utils/iter.ts index dd97c8feef..6271a05969 100644 --- a/src/common/utils/iter.ts +++ b/src/common/utils/iter.ts @@ -185,3 +185,19 @@ export function reduce(src: Iterable, reducer: (acc: R, cur: T) => export function join(src: Iterable, connector = ","): string { 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(src: Iterable, fn: (val: T) => any): boolean { + for (const val of src) { + if (!fn(val)) { + return false; + } + } + + return true; +} diff --git a/src/common/utils/sort-compare.ts b/src/common/utils/sort-compare.ts index 041a239bb5..799c47f957 100644 --- a/src/common/utils/sort-compare.ts +++ b/src/common/utils/sort-compare.ts @@ -24,16 +24,44 @@ import * as iter from "./iter"; import type { RawHelmChart } from "../k8s-api/endpoints/helm-charts.api"; import logger from "../logger"; -export function sortCompare(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(left: T, right: T): Ordering { if (left < right) { - return -1; + return Ordering.LESS; } if (left === right) { - return 0; + return Ordering.EQUAL; } - return 1; + return Ordering.GREATER; } interface ChartVersion { @@ -41,17 +69,17 @@ interface ChartVersion { __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) { return semver.compare(right.__version, left.__version); } if (!left.__version && right.__version) { - return 1; + return Ordering.GREATER; } if (left.__version && !right.__version) { - return -1; + return Ordering.LESS; } return sortCompare(left.version, right.version); diff --git a/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx b/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx index 13770b0abf..03c8c47890 100644 --- a/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx +++ b/src/renderer/components/+workloads-deployments/deployment-replicasets.tsx @@ -71,11 +71,11 @@ export class DeploymentReplicaSets extends React.Component { diff --git a/src/renderer/components/+workloads-pods/pods.tsx b/src/renderer/components/+workloads-pods/pods.tsx index 448bfa75c8..7724e16bf7 100644 --- a/src/renderer/components/+workloads-pods/pods.tsx +++ b/src/renderer/components/+workloads-pods/pods.tsx @@ -31,7 +31,7 @@ import { eventStore } from "../+events/event.store"; import { KubeObjectListLayout } from "../kube-object-list-layout"; import { nodesApi, Pod } from "../../../common/k8s-api/endpoints"; import { StatusBrick } from "../status-brick"; -import { cssNames, stopPropagation } from "../../utils"; +import { cssNames, getConvertedParts, stopPropagation } from "../../utils"; import toPairs from "lodash/toPairs"; import startCase from "lodash/startCase"; import kebabCase from "lodash/kebabCase"; @@ -98,7 +98,7 @@ export class Pods extends React.Component { tableId = "workloads_pods" isConfigurable sortingCallbacks={{ - [columnId.name]: pod => pod.getName(), + [columnId.name]: pod => getConvertedParts(pod.getName()), [columnId.namespace]: pod => pod.getNs(), [columnId.containers]: pod => pod.getContainers().length, [columnId.restarts]: pod => pod.getRestartsCount(), diff --git a/src/renderer/components/table/__tests__/getSorted.test.ts b/src/renderer/components/table/__tests__/getSorted.test.ts new file mode 100644 index 0000000000..7480727ce1 --- /dev/null +++ b/src/renderer/components/table/__tests__/getSorted.test.ts @@ -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); + }); + }); + }); +}); diff --git a/src/renderer/components/table/sorting.ts b/src/renderer/components/table/sorting.ts new file mode 100644 index 0000000000..ca9ea959fc --- /dev/null +++ b/src/renderer/components/table/sorting.ts @@ -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(rawItems: T[], sortingCallback: TableSortCallback | 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; +} diff --git a/src/renderer/components/table/table.tsx b/src/renderer/components/table/table.tsx index bb5e63f570..1d428c62be 100644 --- a/src/renderer/components/table/table.tsx +++ b/src/renderer/components/table/table.tsx @@ -22,9 +22,8 @@ import "./table.scss"; import React from "react"; -import { orderBy } from "lodash"; import { observer } from "mobx-react"; -import { boundMethod, cssNames, noop } from "../../utils"; +import { boundMethod, cssNames } from "../../utils"; import { TableRow, TableRowElem, TableRowProps } from "./table-row"; import { TableHead, TableHeadElem, TableHeadProps } from "./table-head"; import type { TableCellElem } from "./table-cell"; @@ -32,6 +31,7 @@ import { VirtualList } from "../virtual-list"; import { createPageParam } from "../../navigation"; import { getSortParams, setSortParams } from "./table.storage"; import { computed, makeObservable } from "mobx"; +import { getSorted } from "./sorting"; export type TableSortBy = string; export type TableOrderBy = "asc" | "desc" | string; @@ -92,16 +92,22 @@ export class Table extends React.Component> { const { sortable, tableId } = this.props; 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() { return Object.assign({}, this.props.sortByDefault, getSortParams(this.props.tableId)); } renderHead() { - const { sortable, children } = this.props; + const { children } = this.props; const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[]; const headElem: React.ReactElement = content.find(elem => elem.type === TableHead); @@ -109,7 +115,7 @@ export class Table extends React.Component> { return null; } - if (sortable) { + if (this.isSortable) { const columns = React.Children.toArray(headElem.props.children) as TableCellElem[]; return React.cloneElement(headElem, { @@ -136,14 +142,12 @@ export class Table extends React.Component> { return headElem; } - getSorted(items: any[]) { - const { sortBy, orderBy: order } = this.sortParams; - const sortingCallback = this.props.sortable[sortBy] || noop; + getSorted(rawItems: Item[]) { + const { sortBy, orderBy: orderByRaw } = this.sortParams; - return orderBy(items, sortingCallback, order as any); + return getSorted(rawItems, this.props.sortable[sortBy], orderByRaw); } - @boundMethod protected onSort({ sortBy, orderBy }: TableSortParams) { setSortParams(this.props.tableId, { sortBy, orderBy }); const { sortSyncWithUrl, onSort } = this.props; @@ -153,9 +157,7 @@ export class Table extends React.Component> { orderByUrlParam.set(orderBy); } - if (onSort) { - onSort({ sortBy, orderBy }); - } + onSort?.({ sortBy, orderBy }); } @boundMethod @@ -183,12 +185,12 @@ export class Table extends React.Component> { } 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(); let rows: React.ReactElement[] = content.filter(elem => elem.type === TableRow); let sortedItems = rows.length ? rows.map(row => row.props.sortItem) : [...items]; - if (sortable) { + if (this.isSortable) { sortedItems = this.getSorted(sortedItems); if (rows.length) { @@ -228,15 +230,13 @@ export class Table extends React.Component> { } render() { - const { selectable, scrollable, sortable, autoSize, virtual } = this.props; - let { className } = this.props; - - className = cssNames("Table flex column", className, { - selectable, scrollable, sortable, autoSize, virtual, + const { selectable, scrollable, autoSize, virtual, className } = this.props; + const classNames = cssNames("Table flex column", className, { + selectable, scrollable, sortable: this.isSortable, autoSize, virtual, }); return ( -
+
{this.renderHead()} {this.renderRows()}
diff --git a/src/renderer/utils/__tests__/converted-name.test.ts b/src/renderer/utils/__tests__/converted-name.test.ts new file mode 100644 index 0000000000..35ac991b4b --- /dev/null +++ b/src/renderer/utils/__tests__/converted-name.test.ts @@ -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); + }); +}); diff --git a/src/renderer/utils/index.ts b/src/renderer/utils/index.ts index 537aab13ba..5c765d4c8a 100755 --- a/src/renderer/utils/index.ts +++ b/src/renderer/utils/index.ts @@ -22,19 +22,18 @@ // Common usage utils & helpers export * from "../../common/utils"; - -export * from "./cssVar"; -export * from "./cssNames"; 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 "./isReactNode"; -export * from "../../common/utils/convertMemory"; -export * from "../../common/utils/convertCpu"; -export * from "./metricUnitsToNumber"; +export * from "./createStorage"; +export * from "./cssNames"; +export * from "./cssVar"; export * from "./display-booleans"; +export * from "./interval"; export * from "./isMiddleClick"; +export * from "./isReactNode"; +export * from "./metricUnitsToNumber"; +export * from "./name-parts"; +export * from "./prevDefault"; +export * from "./saveFile"; +export * from "./storageHelper"; diff --git a/src/renderer/utils/name-parts.ts b/src/renderer/utils/name-parts.ts new file mode 100644 index 0000000000..e60948bc0f --- /dev/null +++ b/src/renderer/utils/name-parts.ts @@ -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; + }); +}