Skip to content

Commit 3a88e8e

Browse files
authored
Implement advanced search for distribution version on cluster list table (stolostron#3902)
Signed-off-by: Randy Bruno Piverger <rbrunopi@redhat.com>
1 parent 653cf30 commit 3a88e8e

File tree

12 files changed

+371
-112
lines changed

12 files changed

+371
-112
lines changed

frontend/package-lock.json

+14-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"react-error-boundary": "3.1.4",
8989
"react-monaco-editor": "0.41.x",
9090
"screenfull": "^6.0.2",
91+
"semver": "^7.6.3",
9192
"set-value": "^4.1.0",
9293
"string-similarity": "4.0.4",
9394
"svg.js": "2.7.1",
@@ -140,7 +141,7 @@
140141
"@types/react": "^18.2.14",
141142
"@types/react-beforeunload": "^2.1.1",
142143
"@types/react-dom": "^18.2.6",
143-
"@types/semver": "^7.3.13",
144+
"@types/semver": "^7.5.8",
144145
"@types/set-value": "^4.0.1",
145146
"@types/string-similarity": "4.0.0",
146147
"@types/testing-library__jest-dom": "5.14.5",

frontend/public/locales/en/translation.json

+2
Original file line numberDiff line numberDiff line change
@@ -2358,6 +2358,7 @@
23582358
"Select <italic>{{appType}}</italic> to delete {{name}} and all related resources.": "Select <italic>{{appType}}</italic> to delete {{name}} and all related resources.",
23592359
"Select a cluster to view details": "Select a cluster to view details",
23602360
"Select a column": "Select a column",
2361+
"Select a column name to choose an operator": "Select a column name to choose an operator",
23612362
"Select a namespace for the credential": "Select a namespace for the credential",
23622363
"Select a namespace to be able to select policies in that namespace.": "Select a namespace to be able to select policies in that namespace.",
23632364
"Select a policy set": "Select a policy set",
@@ -2372,6 +2373,7 @@
23722373
"Select an active cloud name for the credential.": "Select an active cloud name for the credential.",
23732374
"Select an authentication method": "Select an authentication method",
23742375
"Select an inventory": "Select an inventory",
2376+
"Select an operator": "Select an operator",
23752377
"Select at least one cluster set to deploy application resources.": "Select at least one cluster set to deploy application resources.",
23762378
"Select cluster label": "Select cluster label",
23772379
"Select cluster sets from which to select clusters. If you do not select a cluster set, all clusters are selected from all cluster sets bound to the namespace.": "Select cluster sets from which to select clusters. If you do not select a cluster set, all clusters are selected from all cluster sets bound to the namespace.",

frontend/src/lib/search-utils.test.ts

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/* Copyright Contributors to the Open Cluster Management project */
2+
import { SearchOperator } from '../ui-components/AcmSearchInput'
3+
import { handleStandardComparison, handleSemverOperatorComparison } from './search-utils'
4+
5+
const stringData = {
6+
stringOne: 'Adam',
7+
stringTwo: 'Becky',
8+
stringThree: 'Charlie',
9+
stringFour: 'David',
10+
stringFive: 'Eve',
11+
}
12+
const versionData = {
13+
versionOne: '1.0.0',
14+
versionTwo: '1.5.0',
15+
versionThree: '2.0.0',
16+
versionFour: '2.5.0',
17+
versionIncomplete: '1.',
18+
}
19+
20+
describe('search-utils basic string operator', () => {
21+
const { stringOne, stringTwo, stringThree, stringFour, stringFive } = stringData
22+
it('can determine equals', () => {
23+
expect(handleStandardComparison(stringOne, stringOne, SearchOperator.Equals)).toBeTruthy()
24+
})
25+
it('can determine greater', () => {
26+
expect(handleStandardComparison(stringOne, stringTwo, SearchOperator.GreaterThan)).toBeTruthy()
27+
})
28+
it('can determine less', () => {
29+
expect(handleStandardComparison(stringFour, stringThree, SearchOperator.LessThan)).toBeTruthy()
30+
})
31+
it('can determine greater than or equal to', () => {
32+
expect(handleStandardComparison(stringOne, stringOne, SearchOperator.GreaterThanOrEqualTo)).toBeTruthy()
33+
expect(handleStandardComparison(stringOne, stringTwo, SearchOperator.GreaterThanOrEqualTo)).toBeTruthy()
34+
})
35+
it('can determine less than or equal to', () => {
36+
expect(handleStandardComparison(stringOne, stringOne, SearchOperator.LessThanOrEqualTo)).toBeTruthy()
37+
expect(handleStandardComparison(stringFive, stringOne, SearchOperator.LessThanOrEqualTo)).toBeTruthy()
38+
})
39+
it('can determine non-equals', () => {
40+
expect(handleStandardComparison(stringOne, stringFive, SearchOperator.Equals)).toBeFalsy()
41+
})
42+
})
43+
44+
describe('search-utils sermver operator', () => {
45+
const { versionOne, versionTwo, versionThree, versionFour, versionIncomplete } = versionData
46+
it('can determine greater semver', () => {
47+
expect(handleSemverOperatorComparison(versionFour, versionThree, SearchOperator.GreaterThan)).toBeTruthy()
48+
})
49+
it('can determine less semver', () => {
50+
expect(handleSemverOperatorComparison(versionOne, versionTwo, SearchOperator.LessThan)).toBeTruthy()
51+
})
52+
it('can determine equals semver', () => {
53+
expect(handleSemverOperatorComparison(versionOne, versionOne, SearchOperator.Equals)).toBeTruthy()
54+
})
55+
it('can determine greater than or equal to semver', () => {
56+
expect(handleSemverOperatorComparison(versionThree, versionThree, SearchOperator.GreaterThanOrEqualTo)).toBeTruthy()
57+
expect(handleSemverOperatorComparison(versionFour, versionThree, SearchOperator.GreaterThanOrEqualTo)).toBeTruthy()
58+
})
59+
it('can determine less than or equal to semver', () => {
60+
expect(handleSemverOperatorComparison(versionTwo, versionTwo, SearchOperator.LessThanOrEqualTo)).toBeTruthy()
61+
expect(handleSemverOperatorComparison(versionOne, versionTwo, SearchOperator.LessThanOrEqualTo)).toBeTruthy()
62+
})
63+
it('can determine non-equal semver', () => {
64+
expect(handleSemverOperatorComparison(versionOne, versionTwo, SearchOperator.NotEquals)).toBeTruthy()
65+
})
66+
it('can coerce incomplete semver strings', () => {
67+
expect(handleSemverOperatorComparison(versionIncomplete, versionOne, SearchOperator.Equals)).toBeTruthy()
68+
})
69+
})

frontend/src/lib/search-utils.ts

+51-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,54 @@
11
/* Copyright Contributors to the Open Cluster Management project */
2+
import * as semver from 'semver'
3+
import { SearchOperator } from '../ui-components/AcmSearchInput'
24

3-
export const handleOperatorComparison = (value: string, selectedValue: string) => {
4-
return value.localeCompare(selectedValue) === 0
5+
// handleStandardComparison uses localeCompare's API to evaluate string sort order. We assume values sorted earlier are "greater than"
6+
export const handleStandardComparison = (valueOne: string, valueTwo: string, operator: SearchOperator) => {
7+
switch (operator) {
8+
case SearchOperator.Equals:
9+
return valueOne.localeCompare(valueTwo) === 0
10+
case SearchOperator.GreaterThan:
11+
return valueOne.localeCompare(valueTwo) < 0
12+
case SearchOperator.LessThan:
13+
return valueOne.localeCompare(valueTwo) > 0
14+
case SearchOperator.GreaterThanOrEqualTo:
15+
return valueOne.localeCompare(valueTwo) < 0 || valueOne.localeCompare(valueTwo) === 0
16+
case SearchOperator.LessThanOrEqualTo:
17+
return valueOne.localeCompare(valueTwo) > 0 || valueOne.localeCompare(valueTwo) === 0
18+
case SearchOperator.NotEquals:
19+
return valueOne.localeCompare(valueTwo) !== 0
20+
default:
21+
return false
22+
}
23+
}
24+
25+
// for a given displayVersion string there is a distribution value a ' ' and a semver value, here we divide them and take the semver
26+
export const handleSemverOperatorComparison = (versionOne: string, versionTwo: string, operator: SearchOperator) => {
27+
// Semver coerces the version to a valid semver version if possible, otherwise it returns the original value
28+
const coercedVersionOne = semver.valid(semver.coerce(versionOne)) ?? versionOne
29+
const coercedVersionTwo = semver.valid(semver.coerce(versionTwo)) ?? versionTwo
30+
31+
const validInputSemvers = !!semver.valid(coercedVersionOne) && !!semver.valid(coercedVersionTwo)
32+
if (!validInputSemvers) {
33+
if (operator === SearchOperator.NotEquals) {
34+
return true
35+
}
36+
return false
37+
}
38+
switch (operator) {
39+
case SearchOperator.Equals:
40+
return semver.eq(coercedVersionOne, coercedVersionTwo)
41+
case SearchOperator.GreaterThan:
42+
return semver.gt(coercedVersionOne, coercedVersionTwo)
43+
case SearchOperator.LessThan:
44+
return semver.lt(coercedVersionOne, coercedVersionTwo)
45+
case SearchOperator.GreaterThanOrEqualTo:
46+
return semver.gte(coercedVersionOne, coercedVersionTwo)
47+
case SearchOperator.LessThanOrEqualTo:
48+
return semver.lte(coercedVersionOne, coercedVersionTwo)
49+
case SearchOperator.NotEquals:
50+
return !semver.eq(coercedVersionOne, coercedVersionTwo)
51+
default:
52+
return false
53+
}
554
}

0 commit comments

Comments
 (0)