diff --git a/web/cypress/support/perses/99.coo_rbac_perses_user1.cy.ts b/web/cypress/support/perses/99.coo_rbac_perses_user1.cy.ts index cdd6f6568..1f6f3166c 100644 --- a/web/cypress/support/perses/99.coo_rbac_perses_user1.cy.ts +++ b/web/cypress/support/perses/99.coo_rbac_perses_user1.cy.ts @@ -266,11 +266,14 @@ export function testCOORBACPersesTestsDevUser1(perspective: PerspectiveConfig) { listPersesDashboardsPage.assertCreateButtonIsEnabled(); listPersesDashboardsPage.clickCreateButton(); persesCreateDashboardsPage.createDashboardShouldBeLoaded(); - persesCreateDashboardsPage.assertProjectNotExistsInDropdown('observ-test'); + // Non-viewable projects are absent from the dropdown. persesCreateDashboardsPage.assertProjectNotExistsInDropdown('perses-dev'); - persesCreateDashboardsPage.assertProjectNotExistsInDropdown('openshift-monitoring'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('empty-namespace3'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('empty-namespace4'); + // Viewable but non-creatable projects are listed but creation is denied. + persesCreateDashboardsPage.assertCreateAccessDenied('observ-test'); + persesCreateDashboardsPage.assertCreateAccessDenied('openshift-monitoring'); + // Editable project is selectable and creatable. persesCreateDashboardsPage.assertProjectDropdown('openshift-cluster-observability-operator'); persesCreateDashboardsPage.createDashboardDialogCancelButton(); @@ -434,11 +437,11 @@ export function testCOORBACPersesTestsDevUser1(perspective: PerspectiveConfig) { cy.log(`6.2. Change namespace to observ-test`); cy.changeNamespace('observ-test'); - cy.log(`6.3. Assert Kebab icon is disabled`); + cy.log(`6.3. Assert Rename/Delete row actions are disabled`); listPersesDashboardsPage.filter.byName( persesDashboardsDashboardDropdownPersesDev.PERSES_DASHBOARD_SAMPLE[0], ); - listPersesDashboardsPage.assertKebabIconDisabled(); + listPersesDashboardsPage.assertKebabRowActionsDisabled(); cy.log(`6.4. Change namespace to openshift-cluster-observability-operator`); cy.changeNamespace('openshift-cluster-observability-operator'); @@ -464,8 +467,7 @@ export function testCOORBACPersesTestsDevUser1(perspective: PerspectiveConfig) { persesDashboardsDashboardDropdownPersesDev.PERSES_DASHBOARD_SAMPLE[0], ); listPersesDashboardsPage.countDashboards('1'); - listPersesDashboardsPage.clickKebabIcon(); - listPersesDashboardsPage.assertKebabIconDisabled(); + listPersesDashboardsPage.assertKebabRowActionsDisabled(); listPersesDashboardsPage.clearAllFilters(); cy.log(`6.8. Filter by Project and Name`); @@ -568,11 +570,14 @@ export function testCOORBACPersesTestsDevUser1(perspective: PerspectiveConfig) { listPersesDashboardsPage.clickDuplicateOption(); cy.log(`8.5. Assert project dropdown options`); - listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists('observ-test'); + // Non-viewable projects are absent from the dropdown. listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists('perses-dev'); listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists('empty-namespace3'); listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists('empty-namespace4'); - listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists('openshift-monitoring'); + // Viewable but non-creatable projects are listed but duplication is denied. + listPersesDashboardsPage.assertDuplicateProjectDenied('observ-test'); + listPersesDashboardsPage.assertDuplicateProjectDenied('openshift-monitoring'); + // Editable project is selectable and creatable. listPersesDashboardsPage.assertDuplicateProjectDropdownExists( 'openshift-cluster-observability-operator', ); @@ -648,11 +653,14 @@ export function testCOORBACPersesTestsDevUser1(perspective: PerspectiveConfig) { persesImportDashboardsPage.assertPersesDashboardDetected(); cy.log(`10.4. Verify project dropdown options`); - persesImportDashboardsPage.assertProjectNotExistsInDropdown('observ-test'); + // Non-viewable projects are absent from the dropdown. persesImportDashboardsPage.assertProjectNotExistsInDropdown('perses-dev'); - persesImportDashboardsPage.assertProjectNotExistsInDropdown('openshift-monitoring'); persesImportDashboardsPage.assertProjectNotExistsInDropdown('empty-namespace3'); persesImportDashboardsPage.assertProjectNotExistsInDropdown('empty-namespace4'); + // Viewable but non-creatable projects are listed but import is denied. + persesImportDashboardsPage.assertImportAccessDenied('observ-test'); + persesImportDashboardsPage.assertImportAccessDenied('openshift-monitoring'); + // Editable project is selectable and creatable. persesImportDashboardsPage.assertProjectDropdown('openshift-cluster-observability-operator'); persesImportDashboardsPage.clickCancelButton(); diff --git a/web/cypress/support/perses/99.coo_rbac_perses_user2.cy.ts b/web/cypress/support/perses/99.coo_rbac_perses_user2.cy.ts index 27d5d74e8..4a7bd4a7b 100644 --- a/web/cypress/support/perses/99.coo_rbac_perses_user2.cy.ts +++ b/web/cypress/support/perses/99.coo_rbac_perses_user2.cy.ts @@ -1,5 +1,7 @@ import { persesDashboardsPage } from '../../views/perses-dashboards'; import { listPersesDashboardsPage } from '../../views/perses-dashboards-list-dashboards'; +import { persesCreateDashboardsPage } from '../../views/perses-dashboards-create-dashboard'; +import { persesImportDashboardsPage } from '../../views/perses-dashboards-import-dashboard'; import { persesDashboardsDashboardDropdownCOO, persesDashboardsDashboardDropdownPersesDev, @@ -120,67 +122,68 @@ export function testCOORBACPersesTestsDevUser2(perspective: PerspectiveConfig) { persesDashboardsPage.assertEditButtonIsDisabled(); }); - it(`3.${perspective.name} perspective - Create button validation - Disabled`, () => { + it(`3.${perspective.name} perspective - Create button validation - Access denied`, () => { cy.log(`3.1. use sidebar nav to go to Observe > Dashboards (Perses)`); listPersesDashboardsPage.shouldBeLoaded(perspective.dashboardsPageName); - cy.log(`3.2. Verify Create button is disabled`); - listPersesDashboardsPage.assertCreateButtonIsDisabled(); - - cy.log(`3.3 change namespace to perses-dev`); + cy.log(`3.2 change namespace to perses-dev`); cy.changeNamespace('perses-dev'); - cy.log(`3.4. Verify Create button is disabled`); - listPersesDashboardsPage.assertCreateButtonIsDisabled(); - - cy.log(`3.5. Change namespace to openshift-monitoring`); - cy.changeNamespace('openshift-monitoring'); - listPersesDashboardsPage.assertCreateButtonIsDisabled(); + cy.log(`3.3. Verify Create button is enabled but creation is denied for perses-dev`); + listPersesDashboardsPage.assertCreateButtonIsEnabled(); + listPersesDashboardsPage.clickCreateButton(); + persesCreateDashboardsPage.createDashboardShouldBeLoaded(); + persesCreateDashboardsPage.assertCreateAccessDenied('perses-dev'); + persesCreateDashboardsPage.assertCreateAccessDenied('openshift-monitoring'); + persesCreateDashboardsPage.createDashboardDialogCancelButton(); }); - it(`4.${perspective.name} perspective - Kebab icon - Disabled`, () => { + it(`4.${perspective.name} perspective - Kebab icon - Row actions denied`, () => { cy.log(`4.1. use sidebar nav to go to Observe > Dashboards (Perses)`); listPersesDashboardsPage.shouldBeLoaded(perspective.dashboardsPageName); cy.log(`4.2. Change namespace to perses-dev`); cy.changeNamespace('perses-dev'); - cy.log(`4.3. Assert Kebab icon is disabled`); + cy.log(`4.3. Assert Rename/Delete row actions are disabled`); listPersesDashboardsPage.filter.byName( persesDashboardsDashboardDropdownPersesDev.PERSES_DASHBOARD_SAMPLE[0], ); - listPersesDashboardsPage.assertKebabIconDisabled(); + listPersesDashboardsPage.assertKebabRowActionsDisabled(); + + cy.log(`4.4. Assert Duplicate is blocked by access-denied in the modal`); + listPersesDashboardsPage.assertDuplicateAccessDenied('perses-dev'); listPersesDashboardsPage.clearAllFilters(); - cy.log(`4.4. Change namespace to All Projects`); + cy.log(`4.5. Change namespace to All Projects`); cy.changeNamespace('All Projects'); - cy.log(`4.5. Assert Kebab icon is disabled`); + cy.log(`4.6. Assert Rename/Delete row actions are disabled`); listPersesDashboardsPage.filter.byProject('perses-dev'); listPersesDashboardsPage.filter.byName( persesDashboardsDashboardDropdownPersesDev.PERSES_DASHBOARD_SAMPLE[0], ); listPersesDashboardsPage.countDashboards('1'); - listPersesDashboardsPage.assertKebabIconDisabled(); + listPersesDashboardsPage.assertKebabRowActionsDisabled(); listPersesDashboardsPage.clearAllFilters(); }); - it(`5.${perspective.name} perspective - Import button validation - Disabled`, () => { + it(`5.${perspective.name} perspective - Import button validation - Access denied`, () => { cy.log(`5.1. use sidebar nav to go to Observe > Dashboards (Perses)`); listPersesDashboardsPage.shouldBeLoaded(perspective.dashboardsPageName); cy.log(`5.2. Change namespace to perses-dev`); cy.changeNamespace('perses-dev'); - cy.log(`5.3. Verify Import button is disabled`); - listPersesDashboardsPage.assertImportButtonIsDisabled(); - - cy.log(`5.5. Change namespace to openshift-monitoring`); - cy.changeNamespace('openshift-monitoring'); - listPersesDashboardsPage.assertImportButtonIsDisabled(); - - cy.log(`5.6. Change namespace to All Projects`); - cy.changeNamespace('All Projects'); - listPersesDashboardsPage.assertImportButtonIsDisabled(); + cy.log(`5.3. Verify Import button is enabled but import is denied for perses-dev`); + listPersesDashboardsPage.assertImportButtonIsEnabled(); + listPersesDashboardsPage.clickImportButton(); + persesImportDashboardsPage.importDashboardShouldBeLoaded(); + persesImportDashboardsPage.uploadFile( + './cypress/fixtures/coo/coo140_perses/import/testing-perses-dashboard.json', + ); + persesImportDashboardsPage.assertPersesDashboardDetected(); + persesImportDashboardsPage.assertImportAccessDenied('perses-dev'); + persesImportDashboardsPage.clickCancelButton(); }); } diff --git a/web/cypress/support/perses/99.coo_rbac_perses_user3.cy.ts b/web/cypress/support/perses/99.coo_rbac_perses_user3.cy.ts index d4d2d87d1..2d00dd2b9 100644 --- a/web/cypress/support/perses/99.coo_rbac_perses_user3.cy.ts +++ b/web/cypress/support/perses/99.coo_rbac_perses_user3.cy.ts @@ -62,7 +62,7 @@ export function testCOORBACPersesTestsDevUser3(perspective: PerspectiveConfig) { ); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('observ-test'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('perses-dev'); - persesCreateDashboardsPage.assertProjectNotExistsInDropdown('openshift-monitoring'); + persesCreateDashboardsPage.assertCreateAccessDenied('openshift-monitoring'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('empty-namespace4'); persesCreateDashboardsPage.assertProjectDropdown('empty-namespace3'); persesCreateDashboardsPage.createDashboardDialogCancelButton(); @@ -78,7 +78,7 @@ export function testCOORBACPersesTestsDevUser3(perspective: PerspectiveConfig) { ); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('observ-test'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('perses-dev'); - persesCreateDashboardsPage.assertProjectNotExistsInDropdown('openshift-monitoring'); + persesCreateDashboardsPage.assertCreateAccessDenied('openshift-monitoring'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('empty-namespace4'); persesCreateDashboardsPage.assertProjectDropdown('empty-namespace3'); @@ -95,7 +95,7 @@ export function testCOORBACPersesTestsDevUser3(perspective: PerspectiveConfig) { ); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('observ-test'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('perses-dev'); - persesCreateDashboardsPage.assertProjectNotExistsInDropdown('openshift-monitoring'); + persesCreateDashboardsPage.assertCreateAccessDenied('openshift-monitoring'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('empty-namespace4'); persesCreateDashboardsPage.assertProjectDropdown('empty-namespace3'); persesCreateDashboardsPage.createDashboardDialogCancelButton(); @@ -119,7 +119,7 @@ export function testCOORBACPersesTestsDevUser3(perspective: PerspectiveConfig) { persesCreateDashboardsPage.createDashboardShouldBeLoaded(); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('observ-test'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('perses-dev'); - persesCreateDashboardsPage.assertProjectNotExistsInDropdown('openshift-monitoring'); + persesCreateDashboardsPage.assertCreateAccessDenied('openshift-monitoring'); persesCreateDashboardsPage.assertProjectNotExistsInDropdown('empty-namespace4'); persesCreateDashboardsPage.assertProjectDropdown('empty-namespace3'); persesCreateDashboardsPage.createDashboardDialogCancelButton(); @@ -378,10 +378,10 @@ export function testCOORBACPersesTestsDevUser3(perspective: PerspectiveConfig) { listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists( 'openshift-cluster-observability-operator', ); - listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists('openshift-monitoring'); listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists('observ-test'); listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists('perses-dev'); listPersesDashboardsPage.assertDuplicateProjectDropdownNotExists('empty-namespace4'); + listPersesDashboardsPage.assertDuplicateProjectDenied('openshift-monitoring'); listPersesDashboardsPage.assertDuplicateProjectDropdownExists('empty-namespace3'); cy.log(`6.6. Enter new dashboard name`); @@ -458,8 +458,8 @@ export function testCOORBACPersesTestsDevUser3(perspective: PerspectiveConfig) { ); persesImportDashboardsPage.assertProjectNotExistsInDropdown('observ-test'); persesImportDashboardsPage.assertProjectNotExistsInDropdown('perses-dev'); - persesImportDashboardsPage.assertProjectNotExistsInDropdown('openshift-monitoring'); persesImportDashboardsPage.assertProjectNotExistsInDropdown('empty-namespace4'); + persesImportDashboardsPage.assertImportAccessDenied('openshift-monitoring'); persesImportDashboardsPage.assertProjectDropdown('empty-namespace3'); persesImportDashboardsPage.clickCancelButton(); diff --git a/web/cypress/support/perses/99.coo_rbac_perses_user4.cy.ts b/web/cypress/support/perses/99.coo_rbac_perses_user4.cy.ts index a023cee80..62e2503fc 100644 --- a/web/cypress/support/perses/99.coo_rbac_perses_user4.cy.ts +++ b/web/cypress/support/perses/99.coo_rbac_perses_user4.cy.ts @@ -1,4 +1,6 @@ import { listPersesDashboardsPage } from '../../views/perses-dashboards-list-dashboards'; +import { persesCreateDashboardsPage } from '../../views/perses-dashboards-create-dashboard'; +import { persesImportDashboardsPage } from '../../views/perses-dashboards-import-dashboard'; export interface PerspectiveConfig { name: string; @@ -35,38 +37,51 @@ export function testCOORBACPersesTestsDevUser4(perspective: PerspectiveConfig) { cy.log(`1.2. All Projects validation - Dashboard search - empty state`); cy.changeNamespace('All Projects'); listPersesDashboardsPage.noDashboardsFoundState(); - listPersesDashboardsPage.assertCreateButtonIsDisabled(); + listPersesDashboardsPage.assertCreateButtonIsEnabled(); cy.log(`1.3. empty-namespace4 validation - Dashboard search - empty state`); cy.changeNamespace('empty-namespace4'); listPersesDashboardsPage.noDashboardsFoundState(); - listPersesDashboardsPage.assertCreateButtonIsDisabled(); + listPersesDashboardsPage.assertCreateButtonIsEnabled(); cy.log(`1.4. openshift-monitoring validation - Dashboard search - empty state`); cy.changeNamespace('openshift-monitoring'); listPersesDashboardsPage.noDashboardsFoundState(); - listPersesDashboardsPage.assertCreateButtonIsDisabled(); + listPersesDashboardsPage.assertCreateButtonIsEnabled(); }, ); - it(`2.${perspective.name} perspective - Import button validation - Disabled`, () => { + it(`2.${perspective.name} perspective - Create button validation - Access denied`, () => { cy.log(`2.1. use sidebar nav to go to Observe > Dashboards (Perses)`); listPersesDashboardsPage.noDashboardsFoundState(); cy.log(`2.2 change namespace to empty-namespace4`); cy.changeNamespace('empty-namespace4'); - cy.log(`2.3. Verify Import button is disabled`); - listPersesDashboardsPage.assertImportButtonIsDisabled(); + cy.log(`2.3. Verify Create button is enabled but creation is denied for empty-namespace4`); + listPersesDashboardsPage.assertCreateButtonIsEnabled(); + listPersesDashboardsPage.clickCreateButton(); + persesCreateDashboardsPage.createDashboardShouldBeLoaded(); + persesCreateDashboardsPage.assertCreateAccessDenied('empty-namespace4'); + persesCreateDashboardsPage.createDashboardDialogCancelButton(); + }); + + it(`3.${perspective.name} perspective - Import button validation - Access denied`, () => { + cy.log(`3.1. use sidebar nav to go to Observe > Dashboards (Perses)`); + listPersesDashboardsPage.noDashboardsFoundState(); - cy.log(`2.4. Change namespace to openshift-monitoring`); - cy.changeNamespace('openshift-monitoring'); - cy.log(`2.5. Verify Import button is disabled`); - listPersesDashboardsPage.assertImportButtonIsDisabled(); + cy.log(`3.2 change namespace to empty-namespace4`); + cy.changeNamespace('empty-namespace4'); - cy.log(`2.6. Change namespace to All Projects`); - cy.changeNamespace('All Projects'); - cy.log(`2.7. Verify Import button is disabled`); - listPersesDashboardsPage.assertImportButtonIsDisabled(); + cy.log(`3.3. Verify Import button is enabled but import is denied for empty-namespace4`); + listPersesDashboardsPage.assertImportButtonIsEnabled(); + listPersesDashboardsPage.clickImportButton(); + persesImportDashboardsPage.importDashboardShouldBeLoaded(); + persesImportDashboardsPage.uploadFile( + './cypress/fixtures/coo/coo140_perses/import/testing-perses-dashboard.json', + ); + persesImportDashboardsPage.assertPersesDashboardDetected(); + persesImportDashboardsPage.assertImportAccessDenied('empty-namespace4'); + persesImportDashboardsPage.clickCancelButton(); }); } diff --git a/web/cypress/support/perses/99.coo_rbac_perses_user6.cy.ts b/web/cypress/support/perses/99.coo_rbac_perses_user6.cy.ts index 77a08d1d7..a5bf35d62 100644 --- a/web/cypress/support/perses/99.coo_rbac_perses_user6.cy.ts +++ b/web/cypress/support/perses/99.coo_rbac_perses_user6.cy.ts @@ -1,4 +1,6 @@ import { listPersesDashboardsPage } from '../../views/perses-dashboards-list-dashboards'; +import { persesCreateDashboardsPage } from '../../views/perses-dashboards-create-dashboard'; +import { persesImportDashboardsPage } from '../../views/perses-dashboards-import-dashboard'; export interface PerspectiveConfig { name: string; @@ -29,16 +31,28 @@ export function testCOORBACPersesTestsDevUser6(perspective: PerspectiveConfig) { cy.assertNamespace('empty-namespace3', false); cy.assertNamespace('empty-namespace4', false); - cy.log(`1.2. Create button validation`); - listPersesDashboardsPage.assertCreateButtonIsDisabled(); + cy.log(`1.2. Create button is enabled but no project is selectable, so Create is disabled`); + listPersesDashboardsPage.assertCreateButtonIsEnabled(); + listPersesDashboardsPage.clickCreateButton(); + persesCreateDashboardsPage.createDashboardShouldBeLoaded(); + cy.byPFRole('dialog').find('button').contains('Create').should('be.disabled'); + persesCreateDashboardsPage.createDashboardDialogCancelButton(); }, ); - it(`2.${perspective.name} perspective - Import button validation - Disabled`, () => { + it(`2.${perspective.name} perspective - Import button validation - Access denied`, () => { cy.log(`2.1. use sidebar nav to go to Observe > Dashboards (Perses)`); listPersesDashboardsPage.noDashboardsFoundState(); - cy.log(`2.2. Verify Import button is disabled`); - listPersesDashboardsPage.assertImportButtonIsDisabled(); + cy.log(`2.2. Verify Import button is enabled but import is disabled without a project`); + listPersesDashboardsPage.assertImportButtonIsEnabled(); + listPersesDashboardsPage.clickImportButton(); + persesImportDashboardsPage.importDashboardShouldBeLoaded(); + persesImportDashboardsPage.uploadFile( + './cypress/fixtures/coo/coo140_perses/import/testing-perses-dashboard.json', + ); + persesImportDashboardsPage.assertPersesDashboardDetected(); + cy.byPFRole('dialog').find('button').contains('Import').should('be.disabled'); + persesImportDashboardsPage.clickCancelButton(); }); } diff --git a/web/cypress/views/perses-dashboards-create-dashboard.ts b/web/cypress/views/perses-dashboards-create-dashboard.ts index b3c549672..123b312f2 100644 --- a/web/cypress/views/perses-dashboards-create-dashboard.ts +++ b/web/cypress/views/perses-dashboards-create-dashboard.ts @@ -1,4 +1,9 @@ -import { Classes, IDs, persesAriaLabels } from '@/shared/constants/data-test'; +import { + Classes, + IDs, + persesAriaLabels, + persesDashboardDataTestIDs, +} from '@/shared/constants/data-test'; import { persesCreateDashboard, persesDashboardsModalTitles } from '../fixtures/perses/constants'; export const persesCreateDashboardsPage = { @@ -44,6 +49,23 @@ export const persesCreateDashboardsPage = { cy.byPFRole('dialog').find(Classes.PersesCreateDashboardProjectDropdown).click({ force: true }); }, + /** + * Selects a project the user can view but cannot create dashboards in and asserts the create + * access-denied helper text is shown and the Create button is disabled. + */ + assertCreateAccessDenied: (project: string) => { + cy.log('persesCreateDashboardsPage.assertCreateAccessDenied'); + cy.get('#' + IDs.persesDashboardCreateDashboardName) + .should('be.visible') + .clear() + .type('access-denied-check'); + cy.byPFRole('dialog').find(Classes.PersesCreateDashboardProjectDropdown).click({ force: true }); + cy.byAriaLabel(persesAriaLabels.dialogProjectInput).clear().type(project); + cy.byPFRole('option').contains(project).should('be.visible').click({ force: true }); + cy.byTestID(persesDashboardDataTestIDs.createAccessDeniedHelperText).should('be.visible'); + cy.byPFRole('dialog').find('button').contains('Create').should('be.disabled'); + }, + enterDashboardName: (name: string) => { cy.log('persesCreateDashboardsPage.enterDashboardName'); cy.get('#' + IDs.persesDashboardCreateDashboardName) @@ -72,9 +94,9 @@ export const persesCreateDashboardsPage = { assertDuplicatedNameValidation: () => { cy.log('persesCreateDashboardsPage.assertDuplicatedNameValidation'); - cy.byPFRole('list') - .find('h4') - .should('contain.text', persesCreateDashboard.DIALOG_CREATE_NAME_BKD_VALIDATION) + cy.byPFRole('dialog') + .find('.pf-m-error') + .should('contain.text', persesCreateDashboard.DIALOG_DUPLICATED_NAME_PF_VALIDATION_SUFFIX) .should('be.visible'); }, diff --git a/web/cypress/views/perses-dashboards-import-dashboard.ts b/web/cypress/views/perses-dashboards-import-dashboard.ts index 9c6a89eb6..8958f707b 100644 --- a/web/cypress/views/perses-dashboards-import-dashboard.ts +++ b/web/cypress/views/perses-dashboards-import-dashboard.ts @@ -1,4 +1,9 @@ -import { Classes, IDs, persesAriaLabels } from '@/shared/constants/data-test'; +import { + Classes, + IDs, + persesAriaLabels, + persesDashboardDataTestIDs, +} from '@/shared/constants/data-test'; import { persesDashboardsImportDashboard, persesDashboardsModalTitles, @@ -140,6 +145,17 @@ export const persesImportDashboardsPage = { .click({ force: true }); }, + assertImportAccessDenied: (project: string) => { + cy.log('persesImportDashboardsPage.assertImportAccessDenied'); + cy.byAriaLabel(persesAriaLabels.importDashboardProjectInputButton) + .should('be.visible') + .click({ force: true }); + cy.byAriaLabel(persesAriaLabels.dialogProjectInput).clear().type(project); + cy.byPFRole('option').contains(project).should('be.visible').click({ force: true }); + cy.byTestID(persesDashboardDataTestIDs.createAccessDeniedHelperText).should('be.visible'); + cy.byPFRole('dialog').find('button').contains('Import').should('be.disabled'); + }, + assertFailedToMigrateGrafanaDashboard: () => { cy.log('persesImportDashboardsPage.assertFailedToMigrateGrafanaDashboard'); cy.get('h4') diff --git a/web/cypress/views/perses-dashboards-list-dashboards.ts b/web/cypress/views/perses-dashboards-list-dashboards.ts index 02c40af2b..ace08c8db 100644 --- a/web/cypress/views/perses-dashboards-list-dashboards.ts +++ b/web/cypress/views/perses-dashboards-list-dashboards.ts @@ -7,6 +7,7 @@ import { listPersesDashboardsDataTestIDs, listPersesDashboardsOUIAIDs, persesAriaLabels, + persesDashboardDataTestIDs, } from '@/shared/constants/data-test'; import { listPersesDashboardsEmptyState, @@ -181,14 +182,6 @@ export const listPersesDashboardsPage = { .should('not.have.attr', 'disabled'); }, - assertCreateButtonIsDisabled: () => { - cy.log('persesDashboardsPage.assertCreateButtonIsDisabled'); - cy.byTestID(DataTestIDs.PersesCreateDashboardButton) - .scrollIntoView() - .should('be.visible') - .should('have.attr', 'disabled'); - }, - clickKebabIcon: (index?: number) => { const idx = index !== undefined ? index : 0; cy.log('persesDashboardsPage.clickKebabIcon'); @@ -207,12 +200,34 @@ export const listPersesDashboardsPage = { cy.byPFRole('menuitem').contains('Delete dashboard').should('be.visible'); }, - assertKebabIconDisabled: () => { - cy.log('persesDashboardsPage.assertKebabIconDisabled'); - cy.byAriaLabel(persesAriaLabels.persesDashboardKebabIcon) - .scrollIntoView() - .should('be.visible') - .should('have.attr', 'disabled'); + assertKebabRowActionsDisabled: (index?: number) => { + cy.log('persesDashboardsPage.assertKebabRowActionsDisabled'); + listPersesDashboardsPage.clickKebabIcon(index); + cy.byPFRole('menuitem') + .contains('Rename dashboard') + .should('have.attr', 'aria-disabled', 'true'); + cy.byPFRole('menuitem') + .contains('Delete dashboard') + .should('have.attr', 'aria-disabled', 'true'); + cy.byPFRole('menuitem') + .contains('Duplicate dashboard') + .should('not.have.attr', 'aria-disabled', 'true'); + listPersesDashboardsPage.clickKebabIcon(index); + }, + + assertDuplicateProjectDenied: (project: string) => { + cy.log('persesDashboardsPage.assertDuplicateProjectDenied'); + listPersesDashboardsPage.duplicateDashboardSelectProjectDropdown(project); + cy.byTestID(persesDashboardDataTestIDs.createAccessDeniedHelperText).should('be.visible'); + cy.byPFRole('dialog').find('button').contains('Duplicate').should('be.disabled'); + }, + + assertDuplicateAccessDenied: (project: string) => { + cy.log('persesDashboardsPage.assertDuplicateAccessDenied'); + listPersesDashboardsPage.clickKebabIcon(); + listPersesDashboardsPage.clickDuplicateOption(); + listPersesDashboardsPage.assertDuplicateProjectDenied(project); + listPersesDashboardsPage.duplicateDashboardCancelButton(); }, clickRenameDashboardOption: () => { @@ -411,12 +426,4 @@ export const listPersesDashboardsPage = { .should('be.visible') .should('not.have.attr', 'disabled'); }, - - assertImportButtonIsDisabled: () => { - cy.log('listPersesDashboardsPage.assertImportButtonIsDisabled'); - cy.byAriaLabel(persesAriaLabels.dashboardActionsMenu) - .scrollIntoView() - .should('be.visible') - .should('have.attr', 'disabled'); - }, }; diff --git a/web/locales/en/plugin__monitoring-plugin.json b/web/locales/en/plugin__monitoring-plugin.json index 49b7a3101..369835427 100644 --- a/web/locales/en/plugin__monitoring-plugin.json +++ b/web/locales/en/plugin__monitoring-plugin.json @@ -1,72 +1,10 @@ { - "Recreate silence": "Recreate silence", - "Edit silence": "Edit silence", - "Expire silence": "Expire silence", - "Starts": "Starts", - "Ends": "Ends", - "Expired": "Expired", - "Name": "Name", - "Firing alerts": "Firing alerts", - "State": "State", - "Creator": "Creator", - "Alerts": "Alerts", - "Silences": "Silences", - "Alerting rules": "Alerting rules", - "Alerting": "Alerting", - "Severity": "Severity", - "Namespace": "Namespace", - "Source": "Source", - "Cluster": "Cluster", - "Silence alert": "Silence alert", - "View AI Investigation": "View AI Investigation", - "Loading investigations...": "Loading investigations...", - "User": "User", - "Platform": "Platform", - "Export as CSV": "Export as CSV", - "Description": "Description", - "Active since": "Active since", - "Value": "Value", - "{{name}} details": "{{name}} details", - "Alerting rule details": "Alerting rule details", - "Summary": "Summary", - "Message": "Message", - "Runbook": "Runbook", - "For": "For", - "Expression": "Expression", - "Labels": "Labels", - "Active alerts": "Active alerts", - "None found": "None found", - "Filter by Name": "Filter by Name", - "Alert State": "Alert State", - "Filter by State": "Filter by State", + "Error loading silences from Alertmanager. Some of the alerts below may actually be silenced.": "Error loading silences from Alertmanager. Some of the alerts below may actually be silenced.", "Firing": "Firing", "Pending": "Pending", "Silenced": "Silenced", "Not Firing": "Not Firing", - "Filter by Severity": "Filter by Severity", - "Critical": "Critical", - "Warning": "Warning", - "Info": "Info", - "None": "None", - "Filter by Source": "Filter by Source", - "Label": "Label", - "Filter by Label": "Filter by Label", - "Alert Rules Table": "Alert Rules Table", - "No alerting rules found": "No alerting rules found", - "Alert details": "Alert details", - "Alerting rule": "Alerting rule", - "Silenced by": "Silenced by", - "Pending: ": "Pending: ", - "The alert is active but is waiting for the duration that is specified in the alerting rule before it fires.": "The alert is active but is waiting for the duration that is specified in the alerting rule before it fires.", - "Firing: ": "Firing: ", - "The alert is firing because the alert condition is true and the optional `for` duration has passed. The alert will continue to fire as long as the condition remains true.": "The alert is firing because the alert condition is true and the optional `for` duration has passed. The alert will continue to fire as long as the condition remains true.", - "Silenced: ": "Silenced: ", - "The alert is now silenced for a defined time period. Silences temporarily mute alerts based on a set of label selectors that you define. Notifications will not be sent for alerts that match all the listed values or regular expressions.": "The alert is now silenced for a defined time period. Silences temporarily mute alerts based on a set of label selectors that you define. Notifications will not be sent for alerts that match all the listed values or regular expressions.", - "Alert Name": "Alert Name", - "Total": "Total", - "Filter by Cluster": "Filter by Cluster", - "No alerts found": "No alerts found", - "Error loading silences from Alertmanager. Some of the alerts below may actually be silenced.": "Error loading silences from Alertmanager. Some of the alerts below may actually be silenced.", + "Ends": "Ends", "Since": "Since", "Inspect": "Inspect", "The condition that triggered the alert could have a critical impact. The alert requires immediate attention when fired and is typically paged to an individual or to a critical response team.": "The condition that triggered the alert could have a critical impact. The alert requires immediate attention when fired and is typically paged to an individual or to a critical response team.", @@ -78,9 +16,22 @@ "Platform-level alerts relate only to OpenShift namespaces. OpenShift namespaces provide core OpenShift functionality.": "Platform-level alerts relate only to OpenShift namespaces. OpenShift namespaces provide core OpenShift functionality.", "User: ": "User: ", "User workload alerts relate to user-defined namespaces. These alerts are user-created and are customizable. User workload monitoring can be enabled post-installation to provide observability into your own services.": "User workload alerts relate to user-defined namespaces. These alerts are user-created and are customizable. User workload monitoring can be enabled post-installation to provide observability into your own services.", - "Create silence": "Create silence", - "Overwriting current silence": "Overwriting current silence", - "When changes are saved, the currently existing silence will be expired and a new silence with the new configuration will take its place.": "When changes are saved, the currently existing silence will be expired and a new silence with the new configuration will take its place.", + "Platform": "Platform", + "User": "User", + "Severity": "Severity", + "Critical": "Critical", + "Warning": "Warning", + "Info": "Info", + "None": "None", + "Recreate silence": "Recreate silence", + "Edit silence": "Edit silence", + "Expire silence": "Expire silence", + "Starts": "Starts", + "Expired": "Expired", + "Name": "Name", + "Firing alerts": "Firing alerts", + "State": "State", + "Creator": "Creator", "Invalid date / time": "Invalid date / time", "Datetime": "Datetime", "Select the negative matcher option to update the label value to a not equals matcher.": "Select the negative matcher option to update the label value to a not equals matcher.", @@ -110,6 +61,7 @@ "regular expression": "regular expression", "Label name": "Label name", "Label value": "Label value", + "Value": "Value", "Select all that apply:": "Select all that apply:", "RegEx": "RegEx", "Negative matcher": "Negative matcher", @@ -119,6 +71,65 @@ "Comment": "Comment", "Silence": "Silence", "Cancel": "Cancel", + "Active": "Active", + "Expire Silence": "Expire Silence", + "Are you sure you want to expire this silence?": "Are you sure you want to expire this silence?", + "An error occurred": "An error occurred", + "Source": "Source", + "Filter by Name": "Filter by Name", + "Alert State": "Alert State", + "Filter by State": "Filter by State", + "Filter by Severity": "Filter by Severity", + "Filter by Source": "Filter by Source", + "Label": "Label", + "Filter by Label": "Filter by Label", + "Alerting": "Alerting", + "Alert Rules Table": "Alert Rules Table", + "No alerting rules found": "No alerting rules found", + "Alerts": "Alerts", + "Silences": "Silences", + "Alerting rules": "Alerting rules", + "Description": "Description", + "Active since": "Active since", + "Silence alert": "Silence alert", + "{{name}} details": "{{name}} details", + "Alerting rule details": "Alerting rule details", + "Summary": "Summary", + "Message": "Message", + "Runbook": "Runbook", + "For": "For", + "Expression": "Expression", + "Labels": "Labels", + "Active alerts": "Active alerts", + "None found": "None found", + "Namespace": "Namespace", + "Cluster": "Cluster", + "Alert Name": "Alert Name", + "Total": "Total", + "Filter by Cluster": "Filter by Cluster", + "No alerts found": "No alerts found", + "View AI Investigation": "View AI Investigation", + "Loading investigations...": "Loading investigations...", + "Export as CSV": "Export as CSV", + "Alert details": "Alert details", + "Alerting rule": "Alerting rule", + "Silenced by": "Silenced by", + "Pending: ": "Pending: ", + "The alert is active but is waiting for the duration that is specified in the alerting rule before it fires.": "The alert is active but is waiting for the duration that is specified in the alerting rule before it fires.", + "Firing: ": "Firing: ", + "The alert is firing because the alert condition is true and the optional `for` duration has passed. The alert will continue to fire as long as the condition remains true.": "The alert is firing because the alert condition is true and the optional `for` duration has passed. The alert will continue to fire as long as the condition remains true.", + "Silenced: ": "Silenced: ", + "The alert is now silenced for a defined time period. Silences temporarily mute alerts based on a set of label selectors that you define. Notifications will not be sent for alerts that match all the listed values or regular expressions.": "The alert is now silenced for a defined time period. Silences temporarily mute alerts based on a set of label selectors that you define. Notifications will not be sent for alerts that match all the listed values or regular expressions.", + "Create silence": "Create silence", + "Overwriting current silence": "Overwriting current silence", + "When changes are saved, the currently existing silence will be expired and a new silence with the new configuration will take its place.": "When changes are saved, the currently existing silence will be expired and a new silence with the new configuration will take its place.", + "Silence State": "Silence State", + "Silences Table": "Silences Table", + "Error loading silences from Alertmanager. Alertmanager may be unavailable.": "Error loading silences from Alertmanager. Alertmanager may be unavailable.", + "No silences found": "No silences found", + "Error": "Error", + "Expire {{count}} silence_one": "Expire {{count}} silence", + "Expire {{count}} silence_other": "Expire {{count}} silences", "Silence details": "Silence details", "Actions": "Actions", "Matchers": "Matchers", @@ -129,41 +140,49 @@ "Created by": "Created by", "No Alerts found": "No Alerts found", "View alerting rule": "View alerting rule", - "Silence State": "Silence State", - "Active": "Active", - "Silences Table": "Silences Table", - "Error loading silences from Alertmanager. Alertmanager may be unavailable.": "Error loading silences from Alertmanager. Alertmanager may be unavailable.", - "No silences found": "No silences found", - "Error": "Error", - "Expire {{count}} silence_one": "Expire {{count}} silence", - "Expire {{count}} silence_other": "Expire {{count}} silences", - "Expire Silence": "Expire Silence", - "Are you sure you want to expire this silence?": "Are you sure you want to expire this silence?", - "An error occurred": "An error occurred", - "Restricted access": "Restricted access", - "You don't have access to this section due to cluster policy": "You don't have access to this section due to cluster policy", - "Error details": "Error details", - "No {{label}} found": "No {{label}} found", - "Not found": "Not found", - "Try again": "Try again", - "Error loading {{label}}": "Error loading {{label}}", - "404: Not Found": "404: Not Found", - "{{labels}} content is not available in the catalog at this time due to loading failures.": "{{labels}} content is not available in the catalog at this time due to loading failures.", - "No datapoints found.": "No datapoints found.", - "Filter {{label}}...": "Filter {{label}}...", - "Create new option \"{{option}}\"": "Create new option \"{{option}}\"", - "Filter options": "Filter options", - "Clear input value": "Clear input value", - "No results found": "No results found", + "Alerts Timeline": "Alerts Timeline", + "To view alerts, select an incident from the chart above or from the filters.": "To view alerts, select an incident from the chart above or from the filters.", + "Component": "Component", + "Start": "Start", + "End": "End", + "Resolved": "Resolved", + "Unknown": "Unknown", + "Incidents Timeline": "Incidents Timeline", + "ID": "ID", + "Component(s)": "Component(s)", + "Alert": "Alert", + "No incident selected.": "No incident selected.", + "The incident is critical.": "The incident is critical.", + "The incident might lead to critical.": "The incident might lead to critical.", + "Informative": "Informative", + "The incident is not critical.": "The incident is not critical.", + "The incident is currently firing.": "The incident is currently firing.", + "The incident is not currently firing.": "The incident is not currently firing.", + "Incidents": "Incidents", + "Incident data is updated every few minutes. What you see may be up to 5 minutes old. Refresh the page to view updated information.": "Incident data is updated every few minutes. What you see may be up to 5 minutes old. Refresh the page to view updated information.", + "Clear all filters": "Clear all filters", + "Filter type selection": "Filter type selection", + "Incident ID": "Incident ID", + "Severity filters": "Severity filters", + "State filters": "State filters", + "Incident ID filters": "Incident ID filters", + "Last 1 day": "Last 1 day", + "Last 3 days": "Last 3 days", + "Last 7 days": "Last 7 days", + "Last 15 days": "Last 15 days", + "Show graph": "Show graph", + "Hide graph": "Hide graph", + "component": "component", + "components": "components", "Custom time range": "Custom time range", "From": "From", "To": "To", "Save": "Save", "Dashboards": "Dashboards", "Metrics dashboards": "Metrics dashboards", - "Error Loading Dashboards": "Error Loading Dashboards", "Loading": "Loading", "Error loading card": "Error loading card", + "Dashboard": "Dashboard", "Error loading options": "Error loading options", "Select a dashboard from the dropdown": "Select a dashboard from the dropdown", "panel.styles attribute not found": "panel.styles attribute not found", @@ -179,6 +198,47 @@ "Time range": "Time range", "Refresh interval": "Refresh interval", "Could not parse JSON data for dashboard \"{{dashboard}}\"": "Could not parse JSON data for dashboard \"{{dashboard}}\"", + "Error Loading Dashboards": "Error Loading Dashboards", + "Expression (press Shift+Enter for newlines)": "Expression (press Shift+Enter for newlines)", + "Access restricted.": "Access restricted.", + "Failed to load metrics list.": "Failed to load metrics list.", + "Clear query": "Clear query", + "Queries": "Queries", + "Select query": "Select query", + "Add query": "Add query", + "Collapse all query tables": "Collapse all query tables", + "Expand all query tables": "Expand all query tables", + "Delete all queries": "Delete all queries", + "Show series": "Show series", + "Hide series": "Hide series", + "Disable query": "Disable query", + "Enable query": "Enable query", + "Hide all series": "Hide all series", + "Show all series": "Show all series", + "Query must be enabled": "Query must be enabled", + "Delete query": "Delete query", + "Duplicate query": "Duplicate query", + "Error loading values": "Error loading values", + "No datapoints found.": "No datapoints found.", + "Unselect all": "Unselect all", + "Select all": "Select all", + "Error loading custom data source": "Error loading custom data source", + "An error occurred while loading the custom data source.": "An error occurred while loading the custom data source.", + "No query entered": "No query entered", + "Enter a query in the box below to explore metrics for this cluster.": "Enter a query in the box below to explore metrics for this cluster.", + "Insert example query": "Insert example query", + "Run queries": "Run queries", + "Bytes Binary (KiB, MiB)": "Bytes Binary (KiB, MiB)", + "Bytes Decimal (kb, MB)": "Bytes Decimal (kb, MB)", + "Bytes Binary Per Second (KiB/s, MiB/s)": "Bytes Binary Per Second (KiB/s, MiB/s)", + "Bytes Decimal Per Second (kB/s, MB/s)": "Bytes Decimal Per Second (kB/s, MB/s)", + "Packets Per Second": "Packets Per Second", + "Miliseconds": "Miliseconds", + "Seconds": "Seconds", + "Percentage": "Percentage", + "No Units": "No Units", + "Metrics": "Metrics", + "This dropdown only formats results.": "This dropdown only formats results.", "Rename Dashboard": "Rename Dashboard", "Dashboard name": "Dashboard name", "Renaming...": "Renaming...", @@ -187,8 +247,6 @@ "Failed to create project \"{{project}}\". Please try again.": "Failed to create project \"{{project}}\". Please try again.", "Error creating project: {{error}}": "Error creating project: {{error}}", "Duplicate Dashboard": "Duplicate Dashboard", - "Loading...": "Loading...", - "Failed to load project permissions. Please refresh the page and try again.": "Failed to load project permissions. Please refresh the page and try again.", "Select namespace": "Select namespace", "No namespace found for \"{{filter}}\"": "No namespace found for \"{{filter}}\"", "Duplicate": "Duplicate", @@ -198,16 +256,15 @@ "? This action can not be undone.": "? This action can not be undone.", "Deleting...": "Deleting...", "Delete": "Delete", - "Must be 75 or fewer characters long": "Must be 75 or fewer characters long", - "Dashboard name '{{dashboardName}}' already exists in '{{projectName}}' project!": "Dashboard name '{{dashboardName}}' already exists in '{{projectName}}' project!", - "Checking permissions...": "Checking permissions...", "Create": "Create", "Dashboard actions": "Dashboard actions", "Import": "Import", - "To create dashboards, contact your cluster administrator for permission.": "To create dashboards, contact your cluster administrator for permission.", "Create Dashboard": "Create Dashboard", "my-new-dashboard": "my-new-dashboard", "Select project": "Select project", + "You do not have permission to create dashboards in this project.": "You do not have permission to create dashboards in this project.", + "You do not have permission to edit dashboards in this project.": "You do not have permission to edit dashboards in this project.", + "You do not have permission to delete dashboards in this project.": "You do not have permission to delete dashboards in this project.", "View and manage dashboards.": "View and manage dashboards.", "Unable to detect dashboard format. Please provide a valid Perses or Grafana dashboard.": "Unable to detect dashboard format. Please provide a valid Perses or Grafana dashboard.", "Invalid {{format}}: {{error}}": "Invalid {{format}}: {{error}}", @@ -229,38 +286,55 @@ "Perses dashboard detected.": "Perses dashboard detected.", "2. Select project": "2. Select project", "Importing...": "Importing...", + "No Dashboard Available in Selected Project": "No Dashboard Available in Selected Project", + "To explore data, create a dashboard for this project": "To explore data, create a dashboard for this project", + "No Perses Project Available": "No Perses Project Available", + "To explore data, create a Perses Project": "To explore data, create a Perses Project", + "useToast must be used within ToastProvider": "useToast must be used within ToastProvider", + "Project is required for fetching project dashboards": "Project is required for fetching project dashboards", + "Add To Dashboard": "Add To Dashboard", + "Add to dashboard": "Add to dashboard", + "Query: {{query}}": "Query: {{query}}", "Rename dashboard": "Rename dashboard", "Duplicate dashboard": "Duplicate dashboard", "Delete dashboard": "Delete dashboard", - "You don't have permissions for dashboard actions": "You don't have permissions for dashboard actions", - "Dashboard": "Dashboard", "Project": "Project", "Created on": "Created on", "Last Modified": "Last Modified", "Filter by name": "Filter by name", "Filter by project": "Filter by project", + "No results found": "No results found", "No dashboards found": "No dashboards found", "No results match the filter criteria. Clear filters to show results.": "No results match the filter criteria. Clear filters to show results.", "No Perses dashboards are currently available in this project.": "No Perses dashboards are currently available in this project.", - "Clear all filters": "Clear all filters", "Dashboard not found": "Dashboard not found", "The dashboard \"{{name}}\" was not found in project \"{{project}}\".": "The dashboard \"{{name}}\" was not found in project \"{{project}}\".", "Empty Dashboard": "Empty Dashboard", "To get started add something to your dashboard": "To get started add something to your dashboard", + "Loading...": "Loading...", "Edit": "Edit", - "You don't have permission to edit this dashboard": "You don't have permission to edit this dashboard", - "No Dashboard Available in Selected Project": "No Dashboard Available in Selected Project", - "To explore data, create a dashboard for this project": "To explore data, create a dashboard for this project", - "No Perses Project Available": "No Perses Project Available", - "To explore data, create a Perses Project": "To explore data, create a Perses Project", - "Project is required for fetching project dashboards": "Project is required for fetching project dashboards", - "No projects found": "No projects found", - "No results match the filter criteria.": "No results match the filter criteria.", - "Clear filters": "Clear filters", - "Select project...": "Select project...", - "Projects": "Projects", - "All Projects": "All Projects", - "useToast must be used within ToastProvider": "useToast must be used within ToastProvider", + "Must be 75 or fewer characters long": "Must be 75 or fewer characters long", + "Dashboard name '{{dashboardName}}' already exists in '{{projectName}}' project!": "Dashboard name '{{dashboardName}}' already exists in '{{projectName}}' project!", + "Up": "Up", + "Down": "Down", + "Target details": "Target details", + "Targets": "Targets", + "Error loading service monitor data": "Error loading service monitor data", + "Error loading pod monitor data": "Error loading pod monitor data", + "Endpoint": "Endpoint", + "Last scrape": "Last scrape", + "Scrape failed": "Scrape failed", + "Status": "Status", + "Monitor": "Monitor", + "Text": "Text", + "Last Scrape": "Last Scrape", + "Scrape Duration": "Scrape Duration", + "Search by endpoint or namespace...": "Search by endpoint or namespace...", + "Filter by Status": "Filter by Status", + "Metrics targets": "Metrics targets", + "Error loading latest targets data": "Error loading latest targets data", + "Targets Table": "Targets Table", + "No metrics targets found": "No metrics targets found", "Refresh off": "Refresh off", "{{count}} second_one": "{{count}} second", "{{count}} second_other": "{{count}} seconds", @@ -270,82 +344,7 @@ "{{count}} hour_other": "{{count}} hours", "{{count}} day_one": "{{count}} day", "{{count}} day_other": "{{count}} days", - "Alerts Timeline": "Alerts Timeline", - "To view alerts, select an incident from the chart above or from the filters.": "To view alerts, select an incident from the chart above or from the filters.", - "Component": "Component", - "Start": "Start", - "End": "End", - "Resolved": "Resolved", - "Unknown": "Unknown", - "Incidents Timeline": "Incidents Timeline", - "ID": "ID", - "Component(s)": "Component(s)", - "Alert": "Alert", - "Incidents": "Incidents", - "Incident data is updated every few minutes. What you see may be up to 5 minutes old. Refresh the page to view updated information.": "Incident data is updated every few minutes. What you see may be up to 5 minutes old. Refresh the page to view updated information.", - "Filter type selection": "Filter type selection", - "Incident ID": "Incident ID", - "Severity filters": "Severity filters", - "State filters": "State filters", - "Incident ID filters": "Incident ID filters", - "Last 1 day": "Last 1 day", - "Last 3 days": "Last 3 days", - "Last 7 days": "Last 7 days", - "Last 15 days": "Last 15 days", - "Show graph": "Show graph", - "Hide graph": "Hide graph", - "No incident selected.": "No incident selected.", - "The incident is critical.": "The incident is critical.", - "The incident might lead to critical.": "The incident might lead to critical.", - "Informative": "Informative", - "The incident is not critical.": "The incident is not critical.", - "The incident is currently firing.": "The incident is currently firing.", - "The incident is not currently firing.": "The incident is not currently firing.", - "component": "component", - "components": "components", "No labels": "No labels", - "Expression (press Shift+Enter for newlines)": "Expression (press Shift+Enter for newlines)", - "Access restricted.": "Access restricted.", - "Failed to load metrics list.": "Failed to load metrics list.", - "Clear query": "Clear query", - "Queries": "Queries", - "Select query": "Select query", - "Add query": "Add query", - "Collapse all query tables": "Collapse all query tables", - "Expand all query tables": "Expand all query tables", - "Delete all queries": "Delete all queries", - "Show series": "Show series", - "Hide series": "Hide series", - "Disable query": "Disable query", - "Enable query": "Enable query", - "Hide all series": "Hide all series", - "Show all series": "Show all series", - "Query must be enabled": "Query must be enabled", - "Delete query": "Delete query", - "Duplicate query": "Duplicate query", - "Error loading values": "Error loading values", - "Unselect all": "Unselect all", - "Select all": "Select all", - "Error loading custom data source": "Error loading custom data source", - "An error occurred while loading the custom data source.": "An error occurred while loading the custom data source.", - "No query entered": "No query entered", - "Enter a query in the box below to explore metrics for this cluster.": "Enter a query in the box below to explore metrics for this cluster.", - "Insert example query": "Insert example query", - "Run queries": "Run queries", - "Bytes Binary (KiB, MiB)": "Bytes Binary (KiB, MiB)", - "Bytes Decimal (kb, MB)": "Bytes Decimal (kb, MB)", - "Bytes Binary Per Second (KiB/s, MiB/s)": "Bytes Binary Per Second (KiB/s, MiB/s)", - "Bytes Decimal Per Second (kB/s, MB/s)": "Bytes Decimal Per Second (kB/s, MB/s)", - "Packets Per Second": "Packets Per Second", - "Miliseconds": "Miliseconds", - "Seconds": "Seconds", - "Percentage": "Percentage", - "No Units": "No Units", - "Metrics": "Metrics", - "This dropdown only formats results.": "This dropdown only formats results.", - "Add To Dashboard": "Add To Dashboard", - "Add to dashboard": "Add to dashboard", - "Query: {{query}}": "Query: {{query}}", "graph timespan": "graph timespan", "Reset zoom": "Reset zoom", "Displaying with reduced resolution due to large dataset.": "Displaying with reduced resolution due to large dataset.", @@ -367,24 +366,17 @@ "Current page": "Current page", "Pagination": "Pagination", "of": "of", - "Up": "Up", - "Down": "Down", - "Target details": "Target details", - "Targets": "Targets", - "Error loading service monitor data": "Error loading service monitor data", - "Error loading pod monitor data": "Error loading pod monitor data", - "Endpoint": "Endpoint", - "Last scrape": "Last scrape", - "Scrape failed": "Scrape failed", - "Status": "Status", - "Monitor": "Monitor", - "Text": "Text", - "Last Scrape": "Last Scrape", - "Scrape Duration": "Scrape Duration", - "Search by endpoint or namespace...": "Search by endpoint or namespace...", - "Filter by Status": "Filter by Status", - "Metrics targets": "Metrics targets", - "Error loading latest targets data": "Error loading latest targets data", - "Targets Table": "Targets Table", - "No metrics targets found": "No metrics targets found" -} + "Restricted access": "Restricted access", + "You don't have access to this section due to cluster policy": "You don't have access to this section due to cluster policy", + "Error details": "Error details", + "No {{label}} found": "No {{label}} found", + "Not found": "Not found", + "Try again": "Try again", + "Error loading {{label}}": "Error loading {{label}}", + "404: Not Found": "404: Not Found", + "{{labels}} content is not available in the catalog at this time due to loading failures.": "{{labels}} content is not available in the catalog at this time due to loading failures.", + "Filter {{label}}...": "Filter {{label}}...", + "Create new option \"{{option}}\"": "Create new option \"{{option}}\"", + "Filter options": "Filter options", + "Clear input value": "Clear input value" +} \ No newline at end of file diff --git a/web/src/features/perses-dashboards/components/DashboardActionModals.tsx b/web/src/features/perses-dashboards/components/DashboardActionModals.tsx index 8a17590e5..009635c55 100644 --- a/web/src/features/perses-dashboards/components/DashboardActionModals.tsx +++ b/web/src/features/perses-dashboards/components/DashboardActionModals.tsx @@ -12,7 +12,6 @@ import { ModalFooter, ModalHeader, ModalVariant, - Spinner, Stack, StackItem, TextInput, @@ -20,17 +19,21 @@ import { } from '@patternfly/react-core'; import { ExclamationCircleIcon } from '@patternfly/react-icons'; import { TypeaheadSelect, TypeaheadSelectOption } from '@patternfly/react-templates'; -import { t_global_font_weight_200, t_global_spacer_200 } from '@patternfly/react-tokens'; import { DashboardResource } from '@perses-dev/client'; import { getResourceExtendedDisplayName } from '@perses-dev/components'; -import { CSSProperties, useEffect, useMemo } from 'react'; +import { useEffect, useMemo } from 'react'; import { Controller, FormProvider, SubmitHandler, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; +import { + DashboardDeniedHelperText, + formGroupStyle, + LabelSpacer, + useDashboardProjects, +} from '@/features/perses-dashboards/components/DashboardDialogHelpers'; import { useToast } from '@/features/perses-dashboards/components/ToastProvider'; -import { useEditableProjects } from '@/features/perses-dashboards/hooks/useEditableProjects'; -import { usePerses } from '@/features/perses-dashboards/hooks/usePerses'; +import { usePersesDashboardAccess } from '@/features/perses-dashboards/hooks/usePersesDashboardAccess'; import { createDashboardDialogValidationSchema, CreateDashboardValidationType, @@ -47,14 +50,6 @@ import { import { generateMetadataName } from '@/features/perses-dashboards/utils/dashboard-utils'; import { getDashboardUrl, usePerspective } from '@/shared/hooks/usePerspective'; -export const formGroupStyle = { - fontWeight: t_global_font_weight_200.value, -} as CSSProperties; - -export const LabelSpacer = () => { - return
; -}; - interface ActionModalProps { dashboard: DashboardResource; isOpen: boolean; @@ -73,6 +68,11 @@ export const RenameActionModal = ({ dashboard, isOpen, onClose }: ActionModalPro }); const updateDashboardMutation = useUpdateDashboardMutation(); + const [canUpdate, updateChecking] = usePersesDashboardAccess( + 'update', + dashboard?.metadata?.project ?? null, + ); + const updateDenied = !updateChecking && !canUpdate; if (!dashboard) { return null; @@ -154,6 +154,7 @@ export const RenameActionModal = ({ dashboard, isOpen, onClose }: ActionModalPro )} /> +