From ecea51d4e4e0bcdb266d98464a833efc534297f4 Mon Sep 17 00:00:00 2001 From: Henry Avetisyan Date: Wed, 4 Sep 2024 17:31:28 -0700 Subject: [PATCH 1/2] merge #2700, #2703 and #2705 from master to jetty 1.12.x branch Signed-off-by: Henry Avetisyan --- ui/package-lock.json | 21 ++- ui/package.json | 11 +- ui/pom.xml | 2 +- .../denali/__snapshots__/Input.test.js.snap | 50 ++--- .../group/__snapshots__/GroupRow.test.js.snap | 43 ++++- .../__snapshots__/GroupTable.test.js.snap | 173 ++++++++++++------ .../__tests__/components/role/RoleRow.test.js | 106 +---------- .../components/utils/ReviewUtils.test.js | 130 +++++++++++++ ui/src/components/constants/constants.js | 2 +- ui/src/components/denali/Input.js | 11 ++ ui/src/components/denali/InputDropdown.js | 14 ++ ui/src/components/group/GroupRow.js | 35 +++- ui/src/components/group/GroupTable.js | 32 ++-- ui/src/components/member/AddMember.js | 23 ++- ui/src/components/role/RoleRow.js | 43 +---- ui/src/components/utils/ReviewUtils.js | 57 ++++++ 16 files changed, 486 insertions(+), 267 deletions(-) create mode 100644 ui/src/__tests__/components/utils/ReviewUtils.test.js create mode 100644 ui/src/components/utils/ReviewUtils.js diff --git a/ui/package-lock.json b/ui/package-lock.json index d22ae0f8205..ab3561c05df 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -21,7 +21,7 @@ "@redux-devtools/extension": "3.2.3", "@reduxjs/toolkit": "1.8.5", "async": "3.2.4", - "aws-sdk": "^2.1213.0", + "aws-sdk": "2.1680.0", "axios": "1.6.0", "body-parser": "1.20.0", "cookie-parser": "1.4.6", @@ -38,7 +38,7 @@ "flatpickr": "4.6.13", "helmet": "6.0.0", "immer": "9.0.15", - "jest-when": "^3.6.0", + "jest-when": "3.6.0", "js-cookie": "3.0.1", "lodash": "4.17.21", "lodash.clone": "4.5.0", @@ -76,14 +76,15 @@ "@sinonjs/referee": "9.1.1", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "13.4.0", - "@types/jest": "^29.5.12", + "@types/jest": "29.5.12", "@wdio/cli": "8.31.0", "@wdio/local-runner": "8.31.0", "@wdio/mocha-framework": "8.31.0", "@wdio/sauce-service": "8.31.0", "@wdio/spec-reporter": "8.31.0", "babel-jest": "29.7.0", - "chromedriver": "^120", + "babel-preset-current-node-syntax": "1.0.1", + "chromedriver": "120", "dotenv": "16.0.2", "expect": "29.0.3", "expect-webdriverio": "4.11.2", @@ -3611,9 +3612,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1664.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1664.0.tgz", - "integrity": "sha512-S2IA1cCGz38d8ZKsuQGwlK3LE+9cXFt7OFsSGQtKX1Mc40xFXpiqQy7jX1r0vZIiy9ZMnxeTcBPM28G/yYu2kA==", + "version": "2.1680.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1680.0.tgz", + "integrity": "sha512-XHNyhvVB49DC7Ea7+Gsvy4B+xP7tyry6Gw3b9g/DcilA1xmxxRzu8VE9GJ2rP4jYMc/TkX9FHkp5WLo57sITZA==", "hasInstallScript": true, "dependencies": { "buffer": "4.9.2", @@ -19479,9 +19480,9 @@ } }, "aws-sdk": { - "version": "2.1664.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1664.0.tgz", - "integrity": "sha512-S2IA1cCGz38d8ZKsuQGwlK3LE+9cXFt7OFsSGQtKX1Mc40xFXpiqQy7jX1r0vZIiy9ZMnxeTcBPM28G/yYu2kA==", + "version": "2.1680.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1680.0.tgz", + "integrity": "sha512-XHNyhvVB49DC7Ea7+Gsvy4B+xP7tyry6Gw3b9g/DcilA1xmxxRzu8VE9GJ2rP4jYMc/TkX9FHkp5WLo57sITZA==", "requires": { "buffer": "4.9.2", "events": "1.1.1", diff --git a/ui/package.json b/ui/package.json index d731890adec..aeb44dd5251 100644 --- a/ui/package.json +++ b/ui/package.json @@ -57,7 +57,8 @@ "@redux-devtools/extension": "3.2.3", "@reduxjs/toolkit": "1.8.5", "async": "3.2.4", - "aws-sdk": "^2.1213.0", + "aws-sdk": "2.1680.0", + "axios": "1.6.0", "body-parser": "1.20.0", "cookie-parser": "1.4.6", "cookie-session": "2.0.0", @@ -73,7 +74,7 @@ "flatpickr": "4.6.13", "helmet": "6.0.0", "immer": "9.0.15", - "jest-when": "^3.6.0", + "jest-when": "3.6.0", "js-cookie": "3.0.1", "lodash": "4.17.21", "lodash.clone": "4.5.0", @@ -97,7 +98,6 @@ "redux-devtools-extension": "2.13.9", "redux-mock-store": "1.5.4", "redux-thunk": "2.4.1", - "axios": "1.6.0", "setimmediate": "1.0.5", "terser": "5.15.0", "tslib": "2.4.0", @@ -112,14 +112,15 @@ "@sinonjs/referee": "9.1.1", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "13.4.0", - "@types/jest": "^29.5.12", + "@types/jest": "29.5.12", "@wdio/cli": "8.31.0", "@wdio/local-runner": "8.31.0", "@wdio/mocha-framework": "8.31.0", "@wdio/sauce-service": "8.31.0", "@wdio/spec-reporter": "8.31.0", "babel-jest": "29.7.0", - "chromedriver": "^120", + "babel-preset-current-node-syntax": "1.0.1", + "chromedriver": "120", "dotenv": "16.0.2", "expect": "29.0.3", "expect-webdriverio": "4.11.2", diff --git a/ui/pom.xml b/ui/pom.xml index 05f0c3863e2..966fb21e7c5 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -17,7 +17,7 @@ com.yahoo.athenz athenz - 1.12.1-SNAPSHOT + 1.11.65-SNAPSHOT ../pom.xml diff --git a/ui/src/__tests__/components/denali/__snapshots__/Input.test.js.snap b/ui/src/__tests__/components/denali/__snapshots__/Input.test.js.snap index 054b4065ad0..240370e4c7b 100644 --- a/ui/src/__tests__/components/denali/__snapshots__/Input.test.js.snap +++ b/ui/src/__tests__/components/denali/__snapshots__/Input.test.js.snap @@ -1107,58 +1107,58 @@ exports[`Input should render an input with error 1`] = ` color: #d01111; } -.css-1ibtbft-default-default-normal-default-input-subtitle-makeInputBaseClass.error input, -.css-1ibtbft-default-default-normal-default-input-subtitle-makeInputBaseClass.error input.focused, -.css-1ibtbft-default-default-normal-default-input-subtitle-makeInputBaseClass.error input:active, -.css-1ibtbft-default-default-normal-default-input-subtitle-makeInputBaseClass.error input:focus { +.css-17w65qf-default-default-normal-default-input-subtitle-makeInputBaseClass.error input, +.css-17w65qf-default-default-normal-default-input-subtitle-makeInputBaseClass.error input.focused, +.css-17w65qf-default-default-normal-default-input-subtitle-makeInputBaseClass.error input:active, +.css-17w65qf-default-default-normal-default-input-subtitle-makeInputBaseClass.error input:focus { border-bottom: 2px solid #d01111; } -.css-1ibtbft-default-default-normal-default-input-subtitle-makeInputBaseClass.error .message { +.css-17w65qf-default-default-normal-default-input-subtitle-makeInputBaseClass.error .message { color: #d01111; } -.css-7pw43o-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error input, -.css-7pw43o-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error input.focused, -.css-7pw43o-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error input:active, -.css-7pw43o-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error input:focus { +.css-zv347j-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error input, +.css-zv347j-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error input.focused, +.css-zv347j-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error input:active, +.css-zv347j-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error input:focus { border-bottom: 2px solid #d01111; } -.css-7pw43o-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error .message { +.css-zv347j-default-default-normal-default-input-subtitle-makeInputBaseClass-disabledCss-Input.error .message { color: #d01111; } -.css-75nwcf-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error input, -.css-75nwcf-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error input.focused, -.css-75nwcf-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error input:active, -.css-75nwcf-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error input:focus { +.css-qk5url-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error input, +.css-qk5url-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error input.focused, +.css-qk5url-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error input:active, +.css-qk5url-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error input:focus { border-bottom: 2px solid #d01111; } -.css-75nwcf-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error .message { +.css-qk5url-default-default-normal-default-input-subtitle-makeInputBaseClass-darkCss-Input.error .message { color: #d01111; } -.css-j9fivm-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error input, -.css-j9fivm-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error input.focused, -.css-j9fivm-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error input:active, -.css-j9fivm-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error input:focus { +.css-1pkkru2-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error input, +.css-1pkkru2-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error input.focused, +.css-1pkkru2-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error input:active, +.css-1pkkru2-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error input:focus { border-bottom: 2px solid #d01111; } -.css-j9fivm-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error .message { +.css-1pkkru2-default-default-normal-default-input-subtitle-makeInputBaseClass-darkDisabledCss-Input.error .message { color: #d01111; } -.css-1cov534-default-default-normal-default-input-subtitle-makeInputBaseClass.error input, -.css-1cov534-default-default-normal-default-input-subtitle-makeInputBaseClass.error input.focused, -.css-1cov534-default-default-normal-default-input-subtitle-makeInputBaseClass.error input:active, -.css-1cov534-default-default-normal-default-input-subtitle-makeInputBaseClass.error input:focus { +.css-5jacvs-default-default-normal-default-input-subtitle-makeInputBaseClass.error input, +.css-5jacvs-default-default-normal-default-input-subtitle-makeInputBaseClass.error input.focused, +.css-5jacvs-default-default-normal-default-input-subtitle-makeInputBaseClass.error input:active, +.css-5jacvs-default-default-normal-default-input-subtitle-makeInputBaseClass.error input:focus { border-bottom: 2px solid #d01111; } -.css-1cov534-default-default-normal-default-input-subtitle-makeInputBaseClass.error .message { +.css-5jacvs-default-default-normal-default-input-subtitle-makeInputBaseClass.error .message { color: #d01111; } diff --git a/ui/src/__tests__/components/group/__snapshots__/GroupRow.test.js.snap b/ui/src/__tests__/components/group/__snapshots__/GroupRow.test.js.snap index 292899b4f19..762a6e177cf 100644 --- a/ui/src/__tests__/components/group/__snapshots__/GroupRow.test.js.snap +++ b/ui/src/__tests__/components/group/__snapshots__/GroupRow.test.js.snap @@ -23,7 +23,7 @@ exports[`GroupRow should render 1`] = ` padding-left: 20px; } -.emotion-6 { +.emotion-10 { background-color: #3570f40D; text-align: center; padding: 5px 0 5px 15px; @@ -52,19 +52,19 @@ exports[`GroupRow should render 1`] = ` 2017-08-03 18:44 UTC N/A @@ -92,7 +92,7 @@ exports[`GroupRow should render 1`] = ` @@ -114,7 +114,32 @@ exports[`GroupRow should render 1`] = ` + + + + + + + + + @@ -139,7 +164,7 @@ exports[`GroupRow should render 1`] = ` @@ -164,7 +189,7 @@ exports[`GroupRow should render 1`] = ` @@ -189,7 +214,7 @@ exports[`GroupRow should render 1`] = ` diff --git a/ui/src/__tests__/components/group/__snapshots__/GroupTable.test.js.snap b/ui/src/__tests__/components/group/__snapshots__/GroupTable.test.js.snap index ba0e4ae1044..383a32a1f52 100644 --- a/ui/src/__tests__/components/group/__snapshots__/GroupTable.test.js.snap +++ b/ui/src/__tests__/components/group/__snapshots__/GroupTable.test.js.snap @@ -22,6 +22,19 @@ exports[`GroupTable should render 1`] = ` } .emotion-4 { + text-align: left; + border-bottom: 2px solid #d5d5d5; + color: #9a9a9a; + font-weight: 600; + padding-bottom: 5px; + vertical-align: top; + text-transform: uppercase; + padding: 5px 0 5px 15px; + word-break: break-all; +} + +.emotion-10 { + text-align: center; border-bottom: 2px solid #d5d5d5; color: #9a9a9a; font-weight: 600; @@ -32,7 +45,7 @@ exports[`GroupTable should render 1`] = ` word-break: break-all; } -.emotion-20 { +.emotion-22 { box-sizing: border-box; margin-top: 10px; box-shadow: 0 1px 4px #d9d9d9; @@ -42,25 +55,25 @@ exports[`GroupTable should render 1`] = ` padding: 5px 0 5px 15px; } -.emotion-22 { +.emotion-24 { text-align: left; padding: 5px 0 5px 15px; vertical-align: middle; word-break: break-all; } -.emotion-24 { +.emotion-26 { padding-left: 20px; } -.emotion-26 { +.emotion-32 { text-align: center; padding: 5px 0 5px 15px; vertical-align: middle; word-break: break-all; } -.emotion-32 { +.emotion-34 { fill: #188fff; cursor: pointer; vertical-align: text-bottom; @@ -72,7 +85,7 @@ exports[`GroupTable should render 1`] = ` > + @@ -122,27 +138,32 @@ exports[`GroupTable should render 1`] = ` Members Roles + Review + + Tags Settings History Delete @@ -150,38 +171,38 @@ exports[`GroupTable should render 1`] = ` a Invalid date N/A + + + + + + + + +
b Invalid date N/A + + + + + + + + + { @@ -83,106 +81,4 @@ describe('RoleRow', (object, method) => { fireEvent.mouseEnter(descriptionIcon); await screen.findByText('test description'); }); - - it('getSmallestExpiryOrReview check memberExpiryDays value is picked up', async () => { - const role = {memberExpiryDays: 5, lastReviewedDate: '2024-07-24T10:58:07.533Z'}; - let actualDays = getSmallestExpiryOrReview(role); - - expect(_.isEqual(role.memberExpiryDays, actualDays)).toBeTruthy(); - }); - - it('getSmallestExpiryOrReview check serviceExpiryDays value is picked up', async () => { - const role = {serviceExpiryDays: 9, lastReviewedDate: '2024-07-24T10:58:07.533Z'}; - let actualDays = getSmallestExpiryOrReview(role); - - expect(_.isEqual(role.serviceExpiryDays, actualDays)).toBeTruthy(); - }); - - it('getSmallestExpiryOrReview check memberReviewDays value is picked up', async () => { - const role = {memberReviewDays: 6, lastReviewedDate: '2024-07-24T10:58:07.533Z'}; - let actualDays = getSmallestExpiryOrReview(role); - - expect(_.isEqual(role.memberReviewDays, actualDays)).toBeTruthy(); - }); - - it('getSmallestExpiryOrReview check serviceReviewDays value is picked up', async () => { - const role = {serviceReviewDays: 10, lastReviewedDate: '2024-07-24T10:58:07.533Z'}; - let actualDays = getSmallestExpiryOrReview(role); - - expect(_.isEqual(role.serviceReviewDays, actualDays)).toBeTruthy(); - }); - - it('getSmallestExpiryOrReview check groupReviewDays value is picked up', async () => { - const role = {groupReviewDays: 8, lastReviewedDate: '2024-07-24T10:58:07.533Z'}; - let actualDays = getSmallestExpiryOrReview(role); - - expect(_.isEqual(role.groupReviewDays, actualDays)).toBeTruthy(); - }); - - it('getSmallestExpiryOrReview check groupExpiryDays value is picked up', async () => { - const role = {groupExpiryDays: 7, lastReviewedDate: '2024-07-24T10:58:07.533Z'}; - let actualDays = getSmallestExpiryOrReview(role); - - expect(_.isEqual(role.groupExpiryDays, actualDays)).toBeTruthy(); - }); - - it("getSmallestExpiryOrReview check smallest value picked, non 0, null doesn't produce error", async () => { - const role = { - memberExpiryDays: 0, - serviceExpiryDays: null, - memberReviewDays: 6, - serviceReviewDays: 10, - groupReviewDays: 8, - groupExpiryDays: 7, - lastReviewedDate: '2024-07-24T10:58:07.533Z' - }; - let actualDays = getSmallestExpiryOrReview(role); - - expect(_.isEqual(role.memberReviewDays, actualDays)).toBeTruthy(); - }); - - it("getSmallestExpiryOrReview when no expiry or review days assigned to the role, return 0", async () => { - const role = { - lastReviewedDate: '2024-07-24T10:58:07.533Z' - }; - let actualDays = getSmallestExpiryOrReview(role); - - expect(_.isEqual(0, actualDays)).toBeTruthy(); - }); - - it("isReviewRequired when no expiry or review days assigned to the role, return false", async () => { - const role = { - }; - let reviewRequired = isReviewRequired(role); - - expect(_.isEqual(false, reviewRequired)).toBeTruthy(); - }); - - it("isReviewRequired when lastReviewDate is more than 80% of memberExpiryDate ago - return true", async () => { - const role = { - lastReviewedDate: '2024-07-01T11:59:59.000Z', - memberExpiryDays: '10' - }; - // mock current date - jest.spyOn(moment.prototype, 'utc') - .mockReturnValue(moment('2024-07-09T12:00:00.000Z').utc()); - - let reviewRequired = isReviewRequired(role); - - expect(_.isEqual(true, reviewRequired)).toBeTruthy(); - }); - - it("isReviewRequired when lastReviewDate is less than 80% of memberExpiryDate ago - return false", async () => { - const role = { - lastReviewedDate: '2024-07-01T12:00:01.000Z', - memberExpiryDays: '10' - }; - // mock current date - // jest.spyOn(moment.prototype, 'utc') - // .mockReturnValue(moment('2024-07-09T12:00:00.000Z').utc()); - - let reviewRequired = isReviewRequired(role); - - expect(_.isEqual(false, reviewRequired)).toBeTruthy(); - }); }); diff --git a/ui/src/__tests__/components/utils/ReviewUtils.test.js b/ui/src/__tests__/components/utils/ReviewUtils.test.js new file mode 100644 index 00000000000..3eb0b752ad1 --- /dev/null +++ b/ui/src/__tests__/components/utils/ReviewUtils.test.js @@ -0,0 +1,130 @@ +/* + * Copyright The Athenz Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import moment from 'moment-timezone'; +import { _ } from 'lodash'; +import { + getSmallestExpiryOrReview, + isReviewRequired, +} from '../../../components/utils/ReviewUtils'; + +describe('ReviewUtils', (object, method) => { + + afterAll(() => { + jest.clearAllMocks(); + }); + + + it('getSmallestExpiryOrReview check memberExpiryDays value is picked up', async () => { + const role = { memberExpiryDays: 5, lastReviewedDate: '2024-07-24T10:58:07.533Z' }; + let actualDays = getSmallestExpiryOrReview(role); + + expect(_.isEqual(role.memberExpiryDays, actualDays)).toBeTruthy(); + }); + + it('getSmallestExpiryOrReview check serviceExpiryDays value is picked up', async () => { + const role = { serviceExpiryDays: 9, lastReviewedDate: '2024-07-24T10:58:07.533Z' }; + let actualDays = getSmallestExpiryOrReview(role); + + expect(_.isEqual(role.serviceExpiryDays, actualDays)).toBeTruthy(); + }); + + it('getSmallestExpiryOrReview check memberReviewDays value is picked up', async () => { + const role = { memberReviewDays: 6, lastReviewedDate: '2024-07-24T10:58:07.533Z' }; + let actualDays = getSmallestExpiryOrReview(role); + + expect(_.isEqual(role.memberReviewDays, actualDays)).toBeTruthy(); + }); + + it('getSmallestExpiryOrReview check serviceReviewDays value is picked up', async () => { + const role = { serviceReviewDays: 10, lastReviewedDate: '2024-07-24T10:58:07.533Z' }; + let actualDays = getSmallestExpiryOrReview(role); + + expect(_.isEqual(role.serviceReviewDays, actualDays)).toBeTruthy(); + }); + + it('getSmallestExpiryOrReview check groupReviewDays value is picked up', async () => { + const role = { groupReviewDays: 8, lastReviewedDate: '2024-07-24T10:58:07.533Z' }; + let actualDays = getSmallestExpiryOrReview(role); + + expect(_.isEqual(role.groupReviewDays, actualDays)).toBeTruthy(); + }); + + it('getSmallestExpiryOrReview check groupExpiryDays value is picked up', async () => { + const role = { groupExpiryDays: 7, lastReviewedDate: '2024-07-24T10:58:07.533Z' }; + let actualDays = getSmallestExpiryOrReview(role); + + expect(_.isEqual(role.groupExpiryDays, actualDays)).toBeTruthy(); + }); + + it("getSmallestExpiryOrReview check smallest value picked, non 0, null doesn't produce error", async () => { + const role = { + memberExpiryDays: 0, + serviceExpiryDays: null, + memberReviewDays: 6, + serviceReviewDays: 10, + groupReviewDays: 8, + groupExpiryDays: 7, + lastReviewedDate: '2024-07-24T10:58:07.533Z' + }; + let actualDays = getSmallestExpiryOrReview(role); + + expect(_.isEqual(role.memberReviewDays, actualDays)).toBeTruthy(); + }); + + it("getSmallestExpiryOrReview when no expiry or review days assigned to the role, return 0", async () => { + const role = { + lastReviewedDate: '2024-07-24T10:58:07.533Z' + }; + let actualDays = getSmallestExpiryOrReview(role); + + expect(_.isEqual(0, actualDays)).toBeTruthy(); + }); + + it("isReviewRequired when no expiry or review days assigned to the role, return false", async () => { + const role = {}; + let reviewRequired = isReviewRequired(role); + + expect(_.isEqual(false, reviewRequired)).toBeTruthy(); + }); + + it("isReviewRequired when lastReviewDate is more than 80% of memberExpiryDate ago - return true", async () => { + const role = { + lastReviewedDate: '2024-07-01T11:59:59.000Z', + memberExpiryDays: '10' + }; + // mock current date + jest.spyOn(moment.prototype, 'utc') + .mockReturnValue(moment('2024-07-09T12:00:00.000Z').utc()); + + let reviewRequired = isReviewRequired(role); + + expect(_.isEqual(true, reviewRequired)).toBeTruthy(); + }); + + it("isReviewRequired when lastReviewDate is less than 80% of memberExpiryDate ago - return false", async () => { + const role = { + lastReviewedDate: '2024-07-01T12:00:01.000Z', + memberExpiryDays: '10' + }; + // mock current date + // jest.spyOn(moment.prototype, 'utc') + // .mockReturnValue(moment('2024-07-09T12:00:00.000Z').utc()); + + let reviewRequired = isReviewRequired(role); + + expect(_.isEqual(false, reviewRequired)).toBeTruthy(); + }); +}); diff --git a/ui/src/components/constants/constants.js b/ui/src/components/constants/constants.js index 3aff9dc7fa2..ad3bbdc4eeb 100644 --- a/ui/src/components/constants/constants.js +++ b/ui/src/components/constants/constants.js @@ -278,4 +278,4 @@ export const MEMBER_AUTHORITY_FILTER_DISABLED = 1; export const MEMBER_AUTHORITY_SYSTEM_SUSPENDED = 2; export const MEMBER_ATHENZ_SYSTEM_DISABLED = 4; -export const ROLE_PERCENTAGE_OF_DAYS_TILL_NEXT_REVIEW = 0.2; +export const PERCENTAGE_OF_DAYS_TILL_NEXT_REVIEW = 0.2; diff --git a/ui/src/components/denali/Input.js b/ui/src/components/denali/Input.js index bc65f8e4495..1ad1a3237b3 100644 --- a/ui/src/components/denali/Input.js +++ b/ui/src/components/denali/Input.js @@ -125,6 +125,10 @@ const disabledCss = css` } `; +const boldInputCss = css` + font-weight: bold; +`; + const makeIconClass = (props) => css` & input { padding-right: ${props.size === 'small' ? '28px' : '36px'}; @@ -193,6 +197,7 @@ class Input extends React.PureComponent { noanim, renderIcon, size, + isBold, ...rest } = this.props; @@ -238,6 +243,12 @@ class Input extends React.PureComponent { ${makeIconClass(this.props)}; `; } + if (isBold) { + inputWrapperClass = css` + ${inputWrapperClass}; + ${boldInputCss} + `; + } return (
this.props.onChange(selected)} + {...(this.props.onInputValueChange !== undefined && { + onInputValueChange:(evt) => this.props.onInputValueChange(evt) + })} + {...(this.props.defaultHighlightedIndex !== undefined && { + defaultHighlightedIndex: this.props.defaultHighlightedIndex + })} + {...(this.props.stateReducer !== undefined && { + stateReducer: (state, changes) => this.props.stateReducer(state, changes) + })} > {({ clearSelection, @@ -391,6 +404,7 @@ class InputDropdown extends React.Component { onChange: this.onInputChange, onClick: toggleMenu, onFocus: this.props.onFocus, + isBold: isBoldFont(inputValue, this.props.selectedDropdownValue), })} /> )} diff --git a/ui/src/components/group/GroupRow.js b/ui/src/components/group/GroupRow.js index 778682e846e..5f6b090eebe 100644 --- a/ui/src/components/group/GroupRow.js +++ b/ui/src/components/group/GroupRow.js @@ -26,6 +26,7 @@ import { withRouter } from 'next/router'; import { css, keyframes } from '@emotion/react'; import { deleteGroup } from '../../redux/thunks/groups'; import { connect } from 'react-redux'; +import { isReviewRequired } from '../utils/ReviewUtils'; const TDStyled = styled.td` background-color: ${(props) => props.color}; @@ -169,6 +170,10 @@ class GroupRow extends React.Component { this, `/domain/${this.props.domain}/group/${this.state.name}/roles` ); + let clickReview = this.onClickFunction.bind( + this, + `/domain/${this.props.domain}/group/${this.state.name}/review` + ); let clickTag = this.onClickFunction.bind( this, `/domain/${this.props.domain}/group/${this.state.name}/tags` @@ -209,6 +214,8 @@ class GroupRow extends React.Component { let newGroupAnimation = this.props.domain + '-' + this.state.name === this.props.newGroup; + let reviewRequired = isReviewRequired(group); + rows.push( - + {this.localDate.getLocalDate( group.modified, this.props.timeZone, this.props.timeZone )} - + {group.lastReviewedDate ? this.localDate.getLocalDate( group.lastReviewedDate, @@ -273,6 +280,30 @@ class GroupRow extends React.Component { Roles + + + + + } + > + {reviewRequired ? 'Group Review is required' : 'Review Members'} + + - + - - - - - - + + + + + + + Group - Modified Date - Reviewed Date - Members - Roles - Tags - Settings - History - Delete + Modified Date + Reviewed Date + Members + Roles + Review + Tags + Settings + History + Delete {rows} diff --git a/ui/src/components/member/AddMember.js b/ui/src/components/member/AddMember.js index 4151017e044..8aa94902384 100644 --- a/ui/src/components/member/AddMember.js +++ b/ui/src/components/member/AddMember.js @@ -29,6 +29,7 @@ import { addMember } from '../../redux/thunks/collections'; import InputDropdown from '../denali/InputDropdown'; import MemberUtils from '../utils/MemberUtils'; import { selectAllUsers } from '../../redux/selectors/user'; +import Downshift from 'downshift'; const SectionsDiv = styled.div` width: 760px; @@ -105,10 +106,16 @@ class AddMember extends React.Component { this.dateUtils = new DateUtils(); } + clearStateIfInputDoesntMatchIt(inputVal) { + if (this.state.memberName && this.state.memberName !== inputVal) { + this.setState({['memberName']:''}); + } + } + onSubmit() { if (!this.state.memberName || this.state.memberName.trim() === '') { this.setState({ - errorMessage: 'Member name is required.', + errorMessage: 'Member must be selected in the dropdown.', }); return; } @@ -200,6 +207,16 @@ class AddMember extends React.Component { { + // keep input changes when user clicks outside input + if (changes.type && (changes.type === Downshift.stateChangeTypes.mouseUp + || changes.type === Downshift.stateChangeTypes.blurInput)) { + changes.inputValue = state.inputValue; + } + return changes; + }} category={this.props.category} fluid={true} id='member-name' @@ -211,6 +228,10 @@ class AddMember extends React.Component { ['memberName']: evt ? evt.value : '', }) } + onInputValueChange={(inputVal) => { + // remove value from state if input changed + this.clearStateIfInputDoesntMatchIt(inputVal); + }} placeholder={ this.props.category === 'role' && this.props.collection !== 'admin' diff --git a/ui/src/components/role/RoleRow.js b/ui/src/components/role/RoleRow.js index 1bce9cfd758..a14b9f2dd28 100644 --- a/ui/src/components/role/RoleRow.js +++ b/ui/src/components/role/RoleRow.js @@ -27,8 +27,7 @@ import { css, keyframes } from '@emotion/react'; import { deleteRole } from '../../redux/thunks/roles'; import { connect } from 'react-redux'; import { selectDomainAuditEnabled } from '../../redux/selectors/domainData'; -import moment from 'moment-timezone'; -import { ROLE_PERCENTAGE_OF_DAYS_TILL_NEXT_REVIEW } from '../constants/constants'; +import { isReviewRequired } from '../utils/ReviewUtils'; const TDStyledName = styled.div` background-color: ${(props) => props.color}; @@ -95,46 +94,6 @@ const LeftSpan = styled.span` padding-left: 20px; `; -export function isReviewRequired(role) { - // determine last review or last modified date - const reviewData = role.lastReviewedDate ? {lastReviewedDate: role.lastReviewedDate} - : {lastReviewedDate: role.modified}; - // get smallest expiry or review days value for the role - const smallestExpiryOrReview = getSmallestExpiryOrReview(role); - - if (smallestExpiryOrReview === 0) { - // review or expiry days were not set in settings - no review required - return false; - } - - // get 20% of the smallest review period - reviewData.pct20 = Math.ceil(smallestExpiryOrReview * ROLE_PERCENTAGE_OF_DAYS_TILL_NEXT_REVIEW); - - const lastReviewedDate = moment(reviewData.lastReviewedDate, 'YYYY-MM-DDTHH:mm:ss.SSSZ'); - const now = moment().utc(); - - // check if expiry/review is coming up within 20% of the smallest review/expiry period - return now.subtract(smallestExpiryOrReview, 'days').add(reviewData.pct20, 'days').isAfter(lastReviewedDate); -} - -export function getSmallestExpiryOrReview(role){ - const values = [ - role.memberExpiryDays, - role.memberReviewDays, - role.groupExpiryDays, - role.groupReviewDays, - role.serviceExpiryDays, - role.serviceReviewDays - ].filter(obj => obj > 0); // pick only those that have days set and days > 0 - - if (values.length > 0) { - // pick the one with the smallest days value - return values.reduce((obj1, obj2) => obj1 < obj2 ? - obj1 : obj2); - } - return 0; -} - class RoleRow extends React.Component { constructor(props) { super(props); diff --git a/ui/src/components/utils/ReviewUtils.js b/ui/src/components/utils/ReviewUtils.js new file mode 100644 index 00000000000..12cb899ffe8 --- /dev/null +++ b/ui/src/components/utils/ReviewUtils.js @@ -0,0 +1,57 @@ +/* + * Copyright The Athenz Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PERCENTAGE_OF_DAYS_TILL_NEXT_REVIEW } from '../constants/constants'; +import moment from 'moment-timezone'; + +export function isReviewRequired(roleOrGroup) { + // determine last review or last modified date + const reviewData = roleOrGroup.lastReviewedDate ? {lastReviewedDate: roleOrGroup.lastReviewedDate} + : {lastReviewedDate: roleOrGroup.modified}; + // get smallest expiry or review days value for the role + const smallestExpiryOrReview = getSmallestExpiryOrReview(roleOrGroup); + + if (smallestExpiryOrReview === 0) { + // review or expiry days were not set in settings - no review required + return false; + } + + // get 20% of the smallest review period + reviewData.pct20 = Math.ceil(smallestExpiryOrReview * PERCENTAGE_OF_DAYS_TILL_NEXT_REVIEW); + + const lastReviewedDate = moment(reviewData.lastReviewedDate, 'YYYY-MM-DDTHH:mm:ss.SSSZ'); + const now = moment().utc(); + + // check if expiry/review is coming up within 20% of the smallest review/expiry period + return now.subtract(smallestExpiryOrReview, 'days').add(reviewData.pct20, 'days').isAfter(lastReviewedDate); +} + +export function getSmallestExpiryOrReview(roleOrGroup){ + const values = [ + roleOrGroup.memberExpiryDays, + roleOrGroup.memberReviewDays, + roleOrGroup.groupExpiryDays, + roleOrGroup.groupReviewDays, + roleOrGroup.serviceExpiryDays, + roleOrGroup.serviceReviewDays + ].filter(obj => obj > 0); // pick only those that have days set and days > 0 + + if (values.length > 0) { + // pick the one with the smallest days value + return values.reduce((obj1, obj2) => obj1 < obj2 ? + obj1 : obj2); + } + return 0; +} From bc57fbd8156413c90afa06138e119038f2e08ad7 Mon Sep 17 00:00:00 2001 From: Henry Avetisyan Date: Wed, 4 Sep 2024 17:36:53 -0700 Subject: [PATCH 2/2] reset pom.xml changes Signed-off-by: Henry Avetisyan --- ui/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/pom.xml b/ui/pom.xml index 966fb21e7c5..05f0c3863e2 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -17,7 +17,7 @@ com.yahoo.athenz athenz - 1.11.65-SNAPSHOT + 1.12.1-SNAPSHOT ../pom.xml