From 1a461559cd259837714dcfd5be4a2a2f33dea9f9 Mon Sep 17 00:00:00 2001 From: Robert Hunt Date: Thu, 19 Jun 2025 15:07:27 +0100 Subject: [PATCH 1/4] Replace usage of dashboard layout with GitLab UI variant - Remove vue shared component and specs - Update docs to reference GitLab UI component - Update usages to use the GitLab UI component --- .../dashboard_layout.stories.js | 126 ------------- .../dashboard_layout.vue | 106 ----------- .../fe_guide/dashboard_layout_framework.md | 123 ++++++++++++- .../shared/group_security_dashboard_new.vue | 8 +- .../group_security_dashboard_new_spec.js | 4 +- .../dashboard_layout_spec.js | 174 ------------------ 6 files changed, 128 insertions(+), 413 deletions(-) delete mode 100644 app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.vue delete mode 100644 spec/frontend/vue_shared/components/customizable_dashboard/dashboard_layout_spec.js diff --git a/app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.stories.js b/app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.stories.js index 3b34db6c21e054..e69de29bb2d1d6 100644 --- a/app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.stories.js +++ b/app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.stories.js @@ -1,126 +0,0 @@ -import DashboardLayout from './dashboard_layout.vue'; -import ExtendedDashboardPanel from './extended_dashboard_panel.vue'; - -export default { - component: DashboardLayout, - title: 'vue_shared/components/customizable_dashboard/dashboard_layout', -}; - -const dashboardConfig = { - title: 'Dashboard title', - description: 'Dashboards made easy with a snap grid system', - panels: [ - { - id: '1', - title: 'Dashboard panel', - gridAttributes: { - width: 6, - height: 1, - yPos: 0, - xPos: 3, - }, - }, - { - id: '2', - title: 'Another dashboard panel', - gridAttributes: { - width: 3, - height: 2, - yPos: 1, - xPos: 1, - }, - }, - { - id: '3', - title: 'I can be placed anywhere on the grid', - gridAttributes: { - width: 4, - height: 1, - yPos: 2, - xPos: 7, - }, - }, - ], -}; - -const Template = (args, { argTypes }) => ({ - components: { DashboardLayout, ExtendedDashboardPanel }, - props: Object.keys(argTypes), - template: ` - - - - - `, -}); - -const SlotsTemplate = (args, { argTypes }) => ({ - components: { DashboardLayout }, - props: Object.keys(argTypes), - template: ` - - - - - - - - - - - `, -}); - -export const Default = Template.bind({}); -Default.args = { - config: { ...dashboardConfig }, -}; - -export const EmptyState = Template.bind({}); -EmptyState.args = { - config: { - ...dashboardConfig, - panels: [], - }, -}; - -export const Slots = SlotsTemplate.bind({}); -Slots.args = { - config: { ...dashboardConfig }, -}; diff --git a/app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.vue b/app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.vue deleted file mode 100644 index 74ff4966448002..00000000000000 --- a/app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.vue +++ /dev/null @@ -1,106 +0,0 @@ - - diff --git a/doc/development/fe_guide/dashboard_layout_framework.md b/doc/development/fe_guide/dashboard_layout_framework.md index 494660aea059e8..3673d5c965058e 100644 --- a/doc/development/fe_guide/dashboard_layout_framework.md +++ b/doc/development/fe_guide/dashboard_layout_framework.md @@ -18,11 +18,29 @@ title: Dashboard layout framework {{< /history >}} -The dashboard layout framework is part of a broader effort to standardize dashboards across the platform +The [`dashboard_layout.vue`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.vue) +component provides an easy way to render dashboards using a configuration. This is +part of our broader effort to standardize dashboards across the platform as described in [Epic #13801](https://gitlab.com/groups/gitlab-org/-/epics/13801). For more in depth details on the dashboard layout framework, see the [architecture design document](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/dashboard_layout_framework/). +## Interactive examples + +Try it in your browser using our interactive examples: + +- [dashboard_layout](https://design.gitlab.com/storybook/?path=/docs/dashboards-dashboards-layout--docs) +- [extended_dashboard_panel](https://gitlab-org.gitlab.io/gitlab/storybook/?path=/docs/vue-shared-components-customizable-dashboard-extended-dashboard-panel--docs) + +## When to use this component + +This component should be used when: + +- You want an easy way to create a dashboard interface. +- You want your dashboard to align with our [Pajamas guidelines](https://design.gitlab.com/patterns/dashboards). +- You want to benefit from future add-on features such as customizable layouts with resizable, draggable elements. + +For existing dashboards, follow the [migration guide](#migration-guide) below. ## Rendering dashboards To render dashboard layouts it's recommended to use the [GlDashboardLayout](https://design.gitlab.com/storybook/?path=/docs/dashboards-dashboards-layout--docs) @@ -42,8 +60,111 @@ following components: - [`extended_dashboard_panel.vue`](https://gitlab-org.gitlab.io/gitlab/storybook/?path=/docs/vue-shared-components-extended-dashboard-panel--docs): Extends `GlDashboardPanel` with easy alert styling and i18n strings ## Migration guide +### Filters + +The component provides a `#filters` slot to render your filters in the dashboard +layout. The component does not manage or sync filters and leaves it up to the +consumer to manage this state. + +We expect dashboards using the framework to implement two types of filters: + +- Global filters: Applied to every visualization in the dashboard +- Per-panel filters: Applied to individual panels (future support planned) + +For URL synchronization, you can use the shared [`UrlSync`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/javascripts/vue_shared/components/url_sync.vue) component. + +### Additional slots + +For a full list of supported slots see the [interactive examples](#interactive-examples). + +### Basic implementation + +```vue + Migrating an existing dashboard to the GlDashboardLayout should be relatively + +``` + +### Migration guide + +Migrating an existing dashboard to the `dashboard_layout.vue` should be relatively straightforward. In most cases because you only need to replace the dashboard shell and can keep existing visualizations. A typical migration path could look like this: diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/group_security_dashboard_new.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/group_security_dashboard_new.vue index 670dcc39bda10a..6c1fe9acc326a6 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/group_security_dashboard_new.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/group_security_dashboard_new.vue @@ -1,7 +1,7 @@ diff --git a/ee/spec/frontend/security_dashboard/components/shared/group_security_dashboard_new_spec.js b/ee/spec/frontend/security_dashboard/components/shared/group_security_dashboard_new_spec.js index a8ae3f4fc1526b..ca6fecc349b418 100644 --- a/ee/spec/frontend/security_dashboard/components/shared/group_security_dashboard_new_spec.js +++ b/ee/spec/frontend/security_dashboard/components/shared/group_security_dashboard_new_spec.js @@ -1,7 +1,7 @@ import { nextTick } from 'vue'; +import { GlDashboardLayout } from '@gitlab/ui'; import { markRaw } from '~/lib/utils/vue3compat/mark_raw'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import DashboardLayout from '~/vue_shared/components/customizable_dashboard/dashboard_layout.vue'; import { OPERATORS_OR } from '~/vue_shared/components/filtered_search_bar/constants'; import FilteredSearch from 'ee/security_dashboard/components/shared/security_dashboard_filtered_search/filtered_search.vue'; import GroupSecurityDashboardNew from 'ee/security_dashboard/components/shared/group_security_dashboard_new.vue'; @@ -26,7 +26,7 @@ describe('Group Security Dashboard (new version) - Component', () => { }); }; - const findDashboardLayout = () => wrapper.findComponent(DashboardLayout); + const findDashboardLayout = () => wrapper.findComponent(GlDashboardLayout); const findFilteredSearch = () => wrapper.findComponent(FilteredSearch); const getDashboardConfig = () => findDashboardLayout().props('config'); const getFirstPanel = () => getDashboardConfig().panels[0]; diff --git a/spec/frontend/vue_shared/components/customizable_dashboard/dashboard_layout_spec.js b/spec/frontend/vue_shared/components/customizable_dashboard/dashboard_layout_spec.js deleted file mode 100644 index de5640210a16ec..00000000000000 --- a/spec/frontend/vue_shared/components/customizable_dashboard/dashboard_layout_spec.js +++ /dev/null @@ -1,174 +0,0 @@ -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import DashboardLayout from '~/vue_shared/components/customizable_dashboard/dashboard_layout.vue'; -import GridstackWrapper from '~/vue_shared/components/customizable_dashboard/gridstack_wrapper.vue'; - -const dashboardConfig = { - title: 'Dashboard title', - description: 'This is my dashboard description', - panels: [ - { - id: '1', - title: 'A dashboard panel', - gridAttributes: { - width: 6, - height: 1, - yPos: 0, - xPos: 3, - }, - }, - ], -}; - -describe('CustomizableDashboard', () => { - /** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */ - let wrapper; - - const findTitle = () => wrapper.findByTestId('title'); - const findDescription = () => wrapper.findByTestId('description'); - const findActionsContainer = () => wrapper.findByTestId('actions-container'); - const findFiltersContainer = () => wrapper.findByTestId('filters-container'); - const findGrid = () => wrapper.findComponent(GridstackWrapper); - - const panelSlotSpy = jest.fn(); - const emptyStateSlotSpy = jest.fn(); - - const createWrapper = (props = {}, scopedSlots = {}) => { - wrapper = shallowMountExtended(DashboardLayout, { - propsData: { - config: dashboardConfig, - ...props, - }, - scopedSlots: { - panel: panelSlotSpy, - 'empty-state': emptyStateSlotSpy, - ...scopedSlots, - }, - }); - }; - - afterEach(() => { - panelSlotSpy.mockRestore(); - emptyStateSlotSpy.mockRestore(); - }); - - describe('default behaviour', () => { - beforeEach(() => { - createWrapper(); - }); - - it('renders the dashboard title', () => { - expect(findTitle().text()).toContain('Dashboard title'); - }); - - it('renders the dashboard description', () => { - expect(findDescription().text()).toContain('This is my dashboard description'); - }); - - it('renders the dashboard grid with the config', () => { - expect(findGrid().props('value')).toMatchObject(dashboardConfig); - }); - - it('renders the panel slot for each panel', () => { - expect(panelSlotSpy).toHaveBeenCalledTimes(dashboardConfig.panels.length); - }); - - it('does not render the empty state', () => { - expect(emptyStateSlotSpy).not.toHaveBeenCalled(); - }); - - it('does not render the filter or actions containers', () => { - expect(findFiltersContainer().exists()).toBe(false); - expect(findActionsContainer().exists()).toBe(false); - }); - }); - - describe('when a dashboard has no panels', () => { - beforeEach(() => { - createWrapper({ - config: { - ...dashboardConfig, - panels: undefined, - }, - }); - }); - - it('does not render the dashboard grid', () => { - expect(findGrid().exists()).toBe(false); - }); - - it('renders the empty state', () => { - expect(emptyStateSlotSpy).toHaveBeenCalled(); - }); - }); - - describe('when a dashboard has no description', () => { - beforeEach(() => { - createWrapper({ - config: { - ...dashboardConfig, - description: undefined, - }, - }); - }); - - it('does not render the dashboard description', () => { - expect(findDescription().exists()).toBe(false); - }); - }); - - describe('when a dashboard has title and description slots', () => { - const titleSlotSpy = jest.fn(); - const descriptionSlotSpy = jest.fn(); - - beforeEach(() => { - createWrapper( - {}, - { - title() { - titleSlotSpy(); - return this.$createElement('div'); - }, - description() { - descriptionSlotSpy(); - return this.$createElement('div'); - }, - }, - ); - }); - - afterEach(() => { - titleSlotSpy.mockRestore(); - descriptionSlotSpy.mockRestore(); - }); - - it('renders the title slot and not the config title', () => { - expect(titleSlotSpy).toHaveBeenCalled(); - expect(findTitle().exists()).toBe(false); - }); - - it('renders the description slot and not the config description', () => { - expect(descriptionSlotSpy).toHaveBeenCalled(); - expect(findDescription().exists()).toBe(false); - }); - }); - - describe('when a dashboard has actions slot content', () => { - beforeEach(() => { - createWrapper({}, { actions: '
actions
' }); - }); - - it('renders the action slots', () => { - expect(findActionsContainer().exists()).toBe(true); - }); - }); - - describe('when a dashboard has filters slot content', () => { - beforeEach(() => { - createWrapper({}, { filters: '
filters
' }); - }); - - it('renders the filters container', () => { - expect(findFiltersContainer().exists()).toBe(true); - }); - }); -}); -- GitLab From 8320b57e3049cc97cde0bc4a59c8df9bfe9596e9 Mon Sep 17 00:00:00 2001 From: Robert Hunt Date: Thu, 17 Jul 2025 10:52:00 +0100 Subject: [PATCH 2/4] Update other usages of the old dashboard layout component - Project security dashboard - Compliance center Signed-off-by: Robert Hunt --- .../components/dashboard/compliance_dashboard.vue | 8 ++++---- .../components/shared/project_security_dashboard_new.vue | 8 ++++---- .../components/dashboard/compliance_dashboard_spec.js | 4 ++-- .../shared/project_security_dashboard_new_spec.js | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ee/app/assets/javascripts/compliance_dashboard/components/dashboard/compliance_dashboard.vue b/ee/app/assets/javascripts/compliance_dashboard/components/dashboard/compliance_dashboard.vue index b644ee929e9a8c..90ca52659df643 100644 --- a/ee/app/assets/javascripts/compliance_dashboard/components/dashboard/compliance_dashboard.vue +++ b/ee/app/assets/javascripts/compliance_dashboard/components/dashboard/compliance_dashboard.vue @@ -1,10 +1,10 @@ diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/project_security_dashboard_new.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/project_security_dashboard_new.vue index 9a1feaad90fe47..0de6b407c1d8bc 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/project_security_dashboard_new.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/project_security_dashboard_new.vue @@ -1,12 +1,12 @@ diff --git a/ee/spec/frontend/compliance_dashboard/components/dashboard/compliance_dashboard_spec.js b/ee/spec/frontend/compliance_dashboard/components/dashboard/compliance_dashboard_spec.js index 599ac6c27315ec..8c05bd4058a559 100644 --- a/ee/spec/frontend/compliance_dashboard/components/dashboard/compliance_dashboard_spec.js +++ b/ee/spec/frontend/compliance_dashboard/components/dashboard/compliance_dashboard_spec.js @@ -1,6 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; +import { GlDashboardLayout } from '@gitlab/ui'; import { createAlert } from '~/alert'; import { getSystemColorScheme } from '~/lib/utils/css_utils'; import waitForPromises from 'helpers/wait_for_promises'; @@ -10,7 +11,6 @@ import FrameworkCoverage from 'ee/compliance_dashboard/components/dashboard/fram import FailedRequirements from 'ee/compliance_dashboard/components/dashboard/failed_requirements.vue'; import FailedControls from 'ee/compliance_dashboard/components/dashboard/failed_controls.vue'; import FrameworksNeedsAttention from 'ee/compliance_dashboard/components/dashboard/frameworks_needs_attention.vue'; -import DashboardLayout from '~/vue_shared/components/customizable_dashboard/dashboard_layout.vue'; import frameworkCoverageQuery from 'ee/compliance_dashboard/components/dashboard/graphql/framework_coverage.query.graphql'; import failedRequirementsQuery from 'ee/compliance_dashboard/components/dashboard/graphql/failed_requirements.query.graphql'; import failedControlsQuery from 'ee/compliance_dashboard/components/dashboard/graphql/failed_controls.query.graphql'; @@ -122,7 +122,7 @@ describe('Compliance dashboard', () => { .fn() .mockImplementation(() => new Promise(() => {})); - const getDashboardConfig = () => wrapper.findComponent(DashboardLayout).props('config'); + const getDashboardConfig = () => wrapper.findComponent(GlDashboardLayout).props('config'); function createComponent() { const apolloProvider = createMockApollo([ diff --git a/ee/spec/frontend/security_dashboard/components/shared/project_security_dashboard_new_spec.js b/ee/spec/frontend/security_dashboard/components/shared/project_security_dashboard_new_spec.js index 1280a0e3a0298c..503ded9c7ada07 100644 --- a/ee/spec/frontend/security_dashboard/components/shared/project_security_dashboard_new_spec.js +++ b/ee/spec/frontend/security_dashboard/components/shared/project_security_dashboard_new_spec.js @@ -1,5 +1,5 @@ +import { GlDashboardLayout } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import DashboardLayout from '~/vue_shared/components/customizable_dashboard/dashboard_layout.vue'; import ProjectSecurityDashboardNew from 'ee/security_dashboard/components/shared/project_security_dashboard_new.vue'; import ProjectVulnerabilitiesOverTimePanel from 'ee/security_dashboard/components/shared/project_vulnerabilities_over_time_panel.vue'; @@ -18,7 +18,7 @@ describe('Project Security Dashboard (new version) - Component', () => { }); }; - const findDashboardLayout = () => wrapper.findComponent(DashboardLayout); + const findDashboardLayout = () => wrapper.findComponent(GlDashboardLayout); const getDashboardConfig = () => findDashboardLayout().props('config'); const getFirstPanel = () => getDashboardConfig().panels[0]; -- GitLab From 31a94f662a39913d1ca6dd01ade6e7e6be7f3097 Mon Sep 17 00:00:00 2001 From: Robert Hunt Date: Thu, 17 Jul 2025 11:29:23 +0100 Subject: [PATCH 3/4] Update integration snapshots Signed-off-by: Robert Hunt --- ...ty_dashboard_init_integration_spec.js.snap | 146 +++++++++--------- 1 file changed, 74 insertions(+), 72 deletions(-) diff --git a/ee/spec/frontend_integration/security_dashboard/__snapshots__/security_dashboard_init_integration_spec.js.snap b/ee/spec/frontend_integration/security_dashboard/__snapshots__/security_dashboard_init_integration_spec.js.snap index 6a9fec09e5b88a..d1f6937d6f48c5 100644 --- a/ee/spec/frontend_integration/security_dashboard/__snapshots__/security_dashboard_init_integration_spec.js.snap +++ b/ee/spec/frontend_integration/security_dashboard/__snapshots__/security_dashboard_init_integration_spec.js.snap @@ -235,51 +235,52 @@ exports[`Security Dashboard default states sets up group-level with \`groupSecur >
-
-
-
- - - Vulnerabilities over time - - -
-
-
-
- + + + Vulnerabilities over time + + +
+
+
+
+ +
+
@@ -521,51 +522,52 @@ exports[`Security Dashboard default states sets up project-level with \`projectS >
-
-
-
- - - Vulnerabilities over time - - -
-
-
-
- + + + Vulnerabilities over time + + +
+
+
+
+ +
+
-- GitLab From 157313341a95668c97d54a40925e2c829262ef43 Mon Sep 17 00:00:00 2001 From: Robert Hunt Date: Fri, 18 Jul 2025 09:19:28 +0100 Subject: [PATCH 4/4] Update docs to match master, and remove ref to Vue shared component Also added more examples to the example implementations section --- .../fe_guide/dashboard_layout_framework.md | 131 +----------------- 1 file changed, 5 insertions(+), 126 deletions(-) diff --git a/doc/development/fe_guide/dashboard_layout_framework.md b/doc/development/fe_guide/dashboard_layout_framework.md index 3673d5c965058e..cef3ef2f58358e 100644 --- a/doc/development/fe_guide/dashboard_layout_framework.md +++ b/doc/development/fe_guide/dashboard_layout_framework.md @@ -18,37 +18,17 @@ title: Dashboard layout framework {{< /history >}} -The [`dashboard_layout.vue`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/vue_shared/components/customizable_dashboard/dashboard_layout.vue) -component provides an easy way to render dashboards using a configuration. This is -part of our broader effort to standardize dashboards across the platform +The dashboard layout framework is part of a broader effort to standardize dashboards across the platform as described in [Epic #13801](https://gitlab.com/groups/gitlab-org/-/epics/13801). For more in depth details on the dashboard layout framework, see the [architecture design document](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/dashboard_layout_framework/). -## Interactive examples - -Try it in your browser using our interactive examples: - -- [dashboard_layout](https://design.gitlab.com/storybook/?path=/docs/dashboards-dashboards-layout--docs) -- [extended_dashboard_panel](https://gitlab-org.gitlab.io/gitlab/storybook/?path=/docs/vue-shared-components-customizable-dashboard-extended-dashboard-panel--docs) - -## When to use this component - -This component should be used when: - -- You want an easy way to create a dashboard interface. -- You want your dashboard to align with our [Pajamas guidelines](https://design.gitlab.com/patterns/dashboards). -- You want to benefit from future add-on features such as customizable layouts with resizable, draggable elements. - -For existing dashboards, follow the [migration guide](#migration-guide) below. ## Rendering dashboards To render dashboard layouts it's recommended to use the [GlDashboardLayout](https://design.gitlab.com/storybook/?path=/docs/dashboards-dashboards-layout--docs) component. It provides an easy way to render dashboards using a configuration which aligns with our [Pajamas guidelines](https://design.gitlab.com/patterns/dashboards/). -Note that GlDashboardLayout supplants the deprecated `dashboard_layout.vue` component in the vue shared directory. - ### Panel guidelines You are free to @@ -60,111 +40,8 @@ following components: - [`extended_dashboard_panel.vue`](https://gitlab-org.gitlab.io/gitlab/storybook/?path=/docs/vue-shared-components-extended-dashboard-panel--docs): Extends `GlDashboardPanel` with easy alert styling and i18n strings ## Migration guide -### Filters - -The component provides a `#filters` slot to render your filters in the dashboard -layout. The component does not manage or sync filters and leaves it up to the -consumer to manage this state. - -We expect dashboards using the framework to implement two types of filters: - -- Global filters: Applied to every visualization in the dashboard -- Per-panel filters: Applied to individual panels (future support planned) - -For URL synchronization, you can use the shared [`UrlSync`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/assets/javascripts/vue_shared/components/url_sync.vue) component. - -### Additional slots - -For a full list of supported slots see the [interactive examples](#interactive-examples). - -### Basic implementation - -```vue - Migrating an existing dashboard to the GlDashboardLayout should be relatively - -``` - -### Migration guide - -Migrating an existing dashboard to the `dashboard_layout.vue` should be relatively straightforward. In most cases because you only need to replace the dashboard shell and can keep existing visualizations. A typical migration path could look like this: @@ -172,7 +49,7 @@ and can keep existing visualizations. A typical migration path could look like t 1. Create a new dashboard using GlDashboardLayout and `extended_dashboard_panel.vue`. 1. Create a dashboard config object that mimics your old dashboard layout. 1. Optionally, use GlDashboardLayout's slots to render your dashboard's -filters, actions, or custom title or description. + filters, actions, or custom title or description. 1. Ensure your new dashboard, panels, and visualizations render correctly. 1. Remove the feature flag and your old dashboard. @@ -183,4 +60,6 @@ for an example on how to render existing visualization components using the dash Real world implementations and migrations using the GlDashboardLayout component: -- New security dashboard added in MR [!191974](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/191974) +- New group security dashboard added in MR [!191974](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/191974) +- New project security dashboard added in MR [!197626](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/197626) +- New compliance center added in MR [!195759](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195759) -- GitLab