diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/list/dashboard_list_item.vue b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/list/dashboard_list_item.vue index 811f0e53778f30d23f73de6be2115394c93ead21..b32e1ef7ff3b71f4598102a2869e171796fd4663 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/components/list/dashboard_list_item.vue +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/components/list/dashboard_list_item.vue @@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid'; import { GlIcon, GlBadge, GlLink, GlTruncateText } from '@gitlab/ui'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; +import { DASHBOARD_STATUS_BETA } from '../../constants'; const TRUNCATE_BUTTON_ID = `desc-truncate-btn-${uuidv4()}`; @@ -24,6 +25,9 @@ export default { isBuiltInDashboard() { return 'userDefined' in this.dashboard && !this.dashboard.userDefined; }, + showBetaBadge() { + return this.dashboard?.status === DASHBOARD_STATUS_BETA; + }, redirectHref() { return joinPaths(window.location.pathname, this.dashboard.slug); }, @@ -59,20 +63,30 @@ export default { class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-flex-grow-1" > <div class="gl-display-flex gl-flex-direction-column"> - <gl-link - v-if="dashboard.redirect" - data-testid="dashboard-redirect-link" - :href="redirectHref" - class="gl-font-weight-bold gl-line-height-normal gl-text-decoration-none!" - >{{ dashboard.title }}</gl-link - > - <router-link - v-else - data-testid="dashboard-router-link" - class="gl-font-weight-bold gl-line-height-normal" - :to="dashboard.slug" - >{{ dashboard.title }}</router-link - > + <div class="gl-display-flex gl-align-items-center"> + <gl-link + v-if="dashboard.redirect" + data-testid="dashboard-redirect-link" + :href="redirectHref" + class="gl-font-weight-bold gl-line-height-normal gl-text-decoration-none!" + >{{ dashboard.title }}</gl-link + > + <router-link + v-else + data-testid="dashboard-router-link" + class="gl-font-weight-bold gl-line-height-normal" + :to="dashboard.slug" + >{{ dashboard.title }}</router-link + > + <gl-badge + v-if="showBetaBadge" + data-testid="dashboard-beta-badge" + class="gl-ml-2" + size="sm" + > + {{ __('Beta') }} + </gl-badge> + </div> <gl-truncate-text class="gl-line-height-normal gl-text-gray-500" :toggle-button-props="$options.truncateTextToggleButtonProps" diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/constants.js b/ee/app/assets/javascripts/analytics/analytics_dashboards/constants.js index d030c6e0cf96eb1358203093f6ab6fb39ac3dcac..9119a87363ec9b64c8c3fafe36a32e13008bd52e 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/constants.js +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/constants.js @@ -196,6 +196,8 @@ export const DASHBOARD_REFRESH_MESSAGE = s__( 'Analytics|Refresh the page to try again or see %{linkStart}troubleshooting documentation%{linkEnd}.', ); +export const DASHBOARD_STATUS_BETA = 'beta'; + export const EVENT_LABEL_CREATED_DASHBOARD = 'user_created_custom_dashboard'; export const EVENT_LABEL_EDITED_DASHBOARD = 'user_edited_custom_dashboard'; export const EVENT_LABEL_VIEWED_DASHBOARD_DESIGNER = 'user_viewed_dashboard_designer'; diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/customizable_dashboard.fragment.graphql b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/customizable_dashboard.fragment.graphql index 15611bdc80c0a3b1cd43800af9bc05e50feb277d..5605bf0d3d75bc94acc8c9a6a1bf8817eeac203f 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/customizable_dashboard.fragment.graphql +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/customizable_dashboard.fragment.graphql @@ -4,6 +4,7 @@ fragment CustomizableDashboardFragment on CustomizableDashboardConnection { title description userDefined + status panels { nodes { title diff --git a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/customizable_dashboards.fragment.graphql b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/customizable_dashboards.fragment.graphql index 4362e4c7d1f52d4bea944b954fe97618a0db4e09..60bc90d89be2fee2451db1581e4395aac80c2ae4 100644 --- a/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/customizable_dashboards.fragment.graphql +++ b/ee/app/assets/javascripts/analytics/analytics_dashboards/graphql/fragments/customizable_dashboards.fragment.graphql @@ -4,5 +4,6 @@ fragment CustomizableDashboardsFragment on CustomizableDashboardConnection { title description userDefined + status } } diff --git a/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/customizable_dashboard.vue b/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/customizable_dashboard.vue index f2b412838e1c09d12dedd98547e978de4ac93aa7..b2f00ad730aa1c1d63038e51a2be55fa3a305ff3 100644 --- a/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/customizable_dashboard.vue +++ b/ee/app/assets/javascripts/vue_shared/components/customizable_dashboard/customizable_dashboard.vue @@ -7,12 +7,14 @@ import { slugify } from '~/lib/utils/text_utility'; import { s__, __ } from '~/locale'; import { InternalEvents } from '~/tracking'; import UrlSync, { HISTORY_REPLACE_UPDATE_METHOD } from '~/vue_shared/components/url_sync.vue'; +import BetaBadge from '~/vue_shared/components/badges/beta_badge.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { createNewVisualizationPanel } from 'ee/analytics/analytics_dashboards/utils'; import UsageOverviewBackgroundAggregationWarning from 'ee/analytics/dashboards/components/usage_overview_background_aggregation_warning.vue'; import { EVENT_LABEL_VIEWED_DASHBOARD_DESIGNER, EVENT_LABEL_EXCLUDE_ANONYMISED_USERS, + DASHBOARD_STATUS_BETA, } from 'ee/analytics/analytics_dashboards/constants'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import PanelsBase from './panels_base.vue'; @@ -38,6 +40,7 @@ export default { GlSprintf, PanelsBase, UrlSync, + BetaBadge, AvailableVisualizationsDrawer, GridstackWrapper, UsageOverviewBackgroundAggregationWarning, @@ -142,6 +145,9 @@ export default { showEnableAggregationWarning() { return this.dashboardHasUsageOverviewPanel && !this.overviewCountsAggregationEnabled; }, + showBetaBadge() { + return this.dashboard.status === DASHBOARD_STATUS_BETA; + }, dashboardDescription() { return this.dashboard.description; }, @@ -358,7 +364,10 @@ export default { : s__('Analytics|Edit your dashboard') }} </h2> - <h2 v-else data-testid="dashboard-title" class="gl-my-0">{{ dashboard.title }}</h2> + <div v-else class="gl-display-flex gl-align-items-center"> + <h2 data-testid="dashboard-title" class="gl-my-0">{{ dashboard.title }}</h2> + <beta-badge v-if="showBetaBadge" class="gl-ml-3" /> + </div> <div v-if="showDashboardDescription" class="gl-display-flex gl-mt-3" diff --git a/ee/spec/frontend/analytics/analytics_dashboards/components/list/dashboard_list_item_spec.js b/ee/spec/frontend/analytics/analytics_dashboards/components/list/dashboard_list_item_spec.js index 5b17961aa8d03ff671b5d5bd76c88a53cb18e7c9..7a21a920edf9e8f2f3b4e4520f621f19550024f3 100644 --- a/ee/spec/frontend/analytics/analytics_dashboards/components/list/dashboard_list_item_spec.js +++ b/ee/spec/frontend/analytics/analytics_dashboards/components/list/dashboard_list_item_spec.js @@ -1,4 +1,4 @@ -import { GlBadge, GlIcon, GlTruncateText } from '@gitlab/ui'; +import { GlIcon, GlTruncateText } from '@gitlab/ui'; import { visitUrl } from '~/lib/utils/url_utility'; import DashboardListItem from 'ee/analytics/analytics_dashboards/components/list/dashboard_list_item.vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -20,17 +20,24 @@ const REDIRECTED_DASHBOARD = { slug: '/slug', redirect: true, }; +const BETA_DASHBOARD = { + title: 'title', + description: 'description', + slug: '/slug', + status: 'beta', +}; describe('DashboardsListItem', () => { /** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */ let wrapper; const findIcon = () => wrapper.findComponent(GlIcon); - const findBadge = () => wrapper.findComponent(GlBadge); + const findBuiltInBadge = () => wrapper.findByTestId('dashboard-by-gitlab'); const findListItem = () => wrapper.findByTestId('dashboard-list-item'); const findRedirectLink = () => wrapper.findByTestId('dashboard-redirect-link'); const findRouterLink = () => wrapper.findByTestId('dashboard-router-link'); const findDescriptionTruncate = () => wrapper.findComponent(GlTruncateText); + const findBetaBadge = () => wrapper.findByTestId('dashboard-beta-badge'); const $router = { push: jest.fn(), @@ -72,7 +79,11 @@ describe('DashboardsListItem', () => { }); it('does not render the built in label', () => { - expect(findBadge().exists()).toBe(false); + expect(findBuiltInBadge().exists()).toBe(false); + }); + + it('does not render the `Beta` badge', () => { + expect(findBetaBadge().exists()).toBe(false); }); it('routes to the dashboard when a list item is clicked', async () => { @@ -88,7 +99,7 @@ describe('DashboardsListItem', () => { }); it('renders the dashboard badge', () => { - expect(findBadge().text()).toBe('Created by GitLab'); + expect(findBuiltInBadge().text()).toBe('Created by GitLab'); }); }); @@ -107,4 +118,14 @@ describe('DashboardsListItem', () => { expect(visitUrl).toHaveBeenCalledWith(expect.stringContaining(REDIRECTED_DASHBOARD.slug)); }); }); + + describe('with a beta dashboard', () => { + beforeEach(() => { + createWrapper(BETA_DASHBOARD); + }); + + it('renders the `Beta` badge', () => { + expect(findBetaBadge().exists()).toBe(true); + }); + }); }); diff --git a/ee/spec/frontend/analytics/analytics_dashboards/mock_data.js b/ee/spec/frontend/analytics/analytics_dashboards/mock_data.js index 5e635bc714e8279db9124e94700c8f5a13b38ee4..e40e82aa35c4492bbd67c10ffc15d21ece123260 100644 --- a/ee/spec/frontend/analytics/analytics_dashboards/mock_data.js +++ b/ee/spec/frontend/analytics/analytics_dashboards/mock_data.js @@ -60,6 +60,7 @@ export const getGraphQLDashboard = (options = {}, withPanels = true) => { slug: '', title: '', userDefined: false, + status: null, description: 'Understand your audience', __typename: 'CustomizableDashboard', ...options, diff --git a/ee/spec/frontend/vue_shared/components/customizable_dashboard/customizable_dashboard_spec.js b/ee/spec/frontend/vue_shared/components/customizable_dashboard/customizable_dashboard_spec.js index 1bf43a80fcd417c50eaffb4243bb821785a53a89..0ad65cb41db3b8b2bedc1f4e70227e4c960102f6 100644 --- a/ee/spec/frontend/vue_shared/components/customizable_dashboard/customizable_dashboard_spec.js +++ b/ee/spec/frontend/vue_shared/components/customizable_dashboard/customizable_dashboard_spec.js @@ -2,6 +2,7 @@ import { nextTick } from 'vue'; import { RouterLinkStub } from '@vue/test-utils'; import { GlLink, GlSprintf } from '@gitlab/ui'; import { createAlert } from '~/alert'; +import BetaBadge from '~/vue_shared/components/badges/beta_badge.vue'; import { mockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import CustomizableDashboard from 'ee/vue_shared/components/customizable_dashboard/customizable_dashboard.vue'; @@ -31,6 +32,7 @@ import { stubComponent } from 'helpers/stub_component'; import { dashboard, builtinDashboard, + betaDashboard, mockDateRangeFilterChangePayload, mockUsageOverviewPanel, } from './mock_data'; @@ -125,6 +127,7 @@ describe('CustomizableDashboard', () => { const findGridstackWrapper = () => wrapper.findComponent(GridstackWrapper); const findUsageOverviewAggregationWarning = () => wrapper.findComponent(UsageOverviewBackgroundAggregationWarning); + const findBetaBadge = () => wrapper.findComponent(BetaBadge); const enterDashboardTitle = async (title, titleValidationError = '') => { await findTitleInput().vm.$emit('input', title); @@ -224,6 +227,10 @@ describe('CustomizableDashboard', () => { it('does not show the usage overview aggregation warning', () => { expect(findUsageOverviewAggregationWarning().exists()).toBe(false); }); + + it('does not render the `Beta` badge', () => { + expect(findBetaBadge().exists()).toBe(false); + }); }); describe('when usage overview aggregation is not enabled', () => { @@ -285,6 +292,16 @@ describe('CustomizableDashboard', () => { }); }); + describe('when a dashboard is in beta', () => { + beforeEach(() => { + createWrapper({}, betaDashboard); + }); + + it('renders the `Beta` badge', () => { + expect(findBetaBadge().exists()).toBe(true); + }); + }); + describe('when mounted with the $route.editing param', () => { beforeEach(() => { createWrapper({}, dashboard, {}, { editing: true }); diff --git a/ee/spec/frontend/vue_shared/components/customizable_dashboard/mock_data.js b/ee/spec/frontend/vue_shared/components/customizable_dashboard/mock_data.js index 93f1413b00f3a267e34d388293847a9e6078e2fd..3f0ddd6640c68ab3118b6121df51c6de945c3321 100644 --- a/ee/spec/frontend/vue_shared/components/customizable_dashboard/mock_data.js +++ b/ee/spec/frontend/vue_shared/components/customizable_dashboard/mock_data.js @@ -85,6 +85,21 @@ export const builtinDashboard = { ], }; +export const betaDashboard = { + title: 'Test Dashboard', + description: 'This dashboard is a work-in-progress', + status: 'beta', + panels: [ + { + title: __('Test A'), + gridAttributes: { width: 3, height: 3 }, + visualization: cubeLineChart, + queryOverrides: {}, + id: getUniquePanelId(), + }, + ], +}; + export const mockDateRangeFilterChangePayload = { startDate: new Date('2016-01-01'), endDate: new Date('2016-02-01'),