Commit 1c1117bd authored by Brandon Labuschagne's avatar Brandon Labuschagne Committed by Kushal Pandya

DevOps Adoption - Show total number of features adopted

parent 84dd0ca0
......@@ -38,13 +38,14 @@ collected before this feature is available.
## DevOps Adoption **(ULTIMATE SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247112) in GitLab 13.7 as a [Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta)
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/247112) in GitLab 13.7 as a [Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta).
> - The Overview tab [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330401) in GitLab 14.1.
> - [Deployed behind a feature flag](../../../user/feature_flags.md), disabled by default.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59267) in GitLab 14.0.
> - Enabled on GitLab.com.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-or-enable-devops-adoption). **(ULTIMATE SELF)**
The DevOps Adoption tab shows you which groups within your organization are using the most essential features of GitLab:
DevOps Adoption shows you which groups within your organization are using the most essential features of GitLab:
- Dev
- Issues
......@@ -67,7 +68,7 @@ DevOps Adoption allows you to:
- Identify specific groups that are lagging in their adoption of GitLab so you can help them along in their DevOps journey.
- Find the groups that have adopted certain features and can provide guidance to other groups on how to use those features.
![DevOps Report](img/admin_devops_adoption_v14_0.png)
![DevOps Report](img/admin_devops_adoption_v14_1.png)
### Disable or enable DevOps Adoption
......
......@@ -8,6 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321083) in GitLab 13.11 as a [Beta feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta).
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/333556) in GitLab 14.1.
> - The Overview tab [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330401) in GitLab 14.1.
Prerequisites:
......@@ -38,7 +39,7 @@ With DevOps Adoption you can:
- Identify specific sub-groups that are lagging in their adoption of GitLab so you can help them along in their DevOps journey.
- Find the sub-groups that have adopted certain features and can provide guidance to other sub-groups on how to use those features.
![DevOps Report](img/group_devops_adoption_v14_0.png)
![DevOps Report](img/group_devops_adoption_v14_1.png)
## Enable data processing
......
......@@ -22,6 +22,7 @@ import getGroupsQuery from '../graphql/queries/get_groups.query.graphql';
import { addSegmentsToCache, deleteSegmentsFromCache } from '../utils/cache_updates';
import { shouldPollTableData } from '../utils/helpers';
import DevopsAdoptionAddDropdown from './devops_adoption_add_dropdown.vue';
import DevopsAdoptionOverview from './devops_adoption_overview.vue';
import DevopsAdoptionSection from './devops_adoption_section.vue';
export default {
......@@ -30,6 +31,7 @@ export default {
GlAlert,
DevopsAdoptionAddDropdown,
DevopsAdoptionSection,
DevopsAdoptionOverview,
DevopsScore,
GlTabs,
GlTab,
......@@ -140,7 +142,10 @@ export default {
);
},
tabIndexValues() {
const tabs = this.$options.devopsAdoptionTableConfiguration.map((item) => item.tab);
const tabs = [
'overview',
...this.$options.devopsAdoptionTableConfiguration.map((item) => item.tab),
];
return this.isGroup ? tabs : [...tabs, 'devops-score'];
},
......@@ -295,6 +300,15 @@ export default {
<template>
<div>
<gl-tabs :value="selectedTab" @input="onTabChange">
<gl-tab data-testid="devops-overview-tab">
<template #title>{{ s__('DevopsReport|Overview') }}</template>
<devops-adoption-overview
:loading="isLoadingAdoptionData"
:data="devopsAdoptionEnabledNamespaces"
:timestamp="timestamp"
/>
</gl-tab>
<gl-tab
v-for="tab in $options.devopsAdoptionTableConfiguration"
:key="tab.title"
......
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { sprintf } from '~/locale';
import {
DEVOPS_ADOPTION_TABLE_CONFIGURATION,
DEVOPS_ADOPTION_OVERALL_CONFIGURATION,
TABLE_HEADER_TEXT,
} from '../constants';
import DevopsAdoptionOverviewCard from './devops_adoption_overview_card.vue';
export default {
name: 'DevopsAdoptionOverview',
components: {
DevopsAdoptionOverviewCard,
GlLoadingIcon,
},
props: {
loading: {
type: Boolean,
required: false,
default: false,
},
data: {
type: Object,
required: false,
default: () => ({}),
},
timestamp: {
type: String,
required: true,
},
},
computed: {
featuresData() {
return DEVOPS_ADOPTION_TABLE_CONFIGURATION.map((item) => ({
...item,
featureMeta: item.cols.map((feature) => ({
title: feature.label,
adopted: this.data.nodes?.some((node) =>
node.latestSnapshot ? node.latestSnapshot[feature.key] : false,
),
})),
}));
},
overallData() {
return {
...DEVOPS_ADOPTION_OVERALL_CONFIGURATION,
featureMeta: this.featuresData.reduce(
(features, section) => [...features, ...section.featureMeta],
[],
),
displayMeta: false,
};
},
overviewData() {
return [this.overallData, ...this.featuresData];
},
headerText() {
return sprintf(TABLE_HEADER_TEXT, { timestamp: this.timestamp });
},
},
};
</script>
<template>
<gl-loading-icon v-if="loading" size="md" class="gl-mt-5" />
<div v-else data-testid="overview-container">
<p class="gl-text-gray-400 gl-my-3" data-testid="overview-container-header">{{ headerText }}</p>
<div
class="gl-display-flex gl-justify-content-space-between gl-flex-direction-column gl-md-flex-direction-row gl-mt-5"
>
<devops-adoption-overview-card
v-for="item in overviewData"
:key="item.title"
class="gl-mb-5"
:icon="item.icon"
:title="item.title"
:variant="item.variant"
:feature-meta="item.featureMeta"
:display-meta="item.displayMeta"
/>
</div>
</div>
</template>
<script>
import { GlIcon, GlProgressBar } from '@gitlab/ui';
import { sprintf } from '~/locale';
import {
DEVOPS_ADOPTION_FEATURES_ADOPTED_TEXT,
DEVOPS_ADOPTION_PROGRESS_BAR_HEIGHT,
} from '../constants';
import DevopsAdoptionTableCellFlag from './devops_adoption_table_cell_flag.vue';
export default {
name: 'DevopsAdoptionOverviewCard',
progressBarHeight: DEVOPS_ADOPTION_PROGRESS_BAR_HEIGHT,
components: {
GlIcon,
GlProgressBar,
DevopsAdoptionTableCellFlag,
},
props: {
icon: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
variant: {
type: String,
required: false,
default: 'primary',
},
featureMeta: {
type: Array,
required: false,
default: () => [],
},
displayMeta: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
featuresCount() {
return this.featureMeta.length;
},
adoptedCount() {
return this.featureMeta.filter((feature) => feature.adopted).length;
},
description() {
return sprintf(DEVOPS_ADOPTION_FEATURES_ADOPTED_TEXT, {
adoptedCount: this.adoptedCount,
featuresCount: this.featuresCount,
title: this.displayMeta ? this.title : '',
});
},
},
};
</script>
<template>
<div
class="devops-overview-card gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-md-mr-5 gl-mb-4"
>
<div class="gl-display-flex gl-align-items-center gl-mb-3" data-testid="card-title">
<gl-icon :name="icon" class="gl-mr-3 gl-text-gray-500" />
<span class="gl-font-md gl-font-weight-bold" data-testid="card-title-text">{{ title }}</span>
</div>
<gl-progress-bar
:value="adoptedCount"
:max="featuresCount"
class="gl-mb-2 gl-md-mr-5"
:variant="variant"
:height="$options.progressBarHeight"
/>
<div class="gl-text-gray-400 gl-mb-1" data-testid="card-description">{{ description }}</div>
<template v-if="displayMeta">
<div
v-for="feature in featureMeta"
:key="feature.title"
class="gl-display-flex gl-align-items-center gl-mt-2"
data-testid="card-meta-row"
>
<devops-adoption-table-cell-flag
:enabled="feature.adopted"
:variant="variant"
class="gl-mr-3"
/>
<span class="gl-text-gray-600 gl-font-sm" data-testid="card-meta-row-title">{{
feature.title
}}</span>
</div>
</template>
</div>
</template>
......@@ -65,15 +65,15 @@ export default {
<template>
<gl-loading-icon v-if="isLoading" size="md" class="gl-my-5" />
<div v-else-if="hasSegmentsData" class="gl-mt-3">
<div class="gl-my-3" data-testid="tableHeader">
<span class="gl-text-gray-400">
<div class="gl-mb-3" data-testid="tableHeader">
<p class="gl-text-gray-400">
<gl-sprintf :message="$options.i18n.tableHeaderText">
<template #timestamp>{{ timestamp }}</template>
</gl-sprintf>
</span>
</p>
<devops-adoption-add-dropdown
class="gl-mt-4 gl-mb-3 gl-md-display-none"
class="gl-mb-3 gl-md-display-none"
:search-term="searchTerm"
:groups="disabledGroupNodes"
:is-loading-groups="isLoadingGroups"
......
......@@ -6,6 +6,8 @@ export const PER_PAGE = 20;
export const DEBOUNCE_DELAY = 500;
export const DEVOPS_ADOPTION_PROGRESS_BAR_HEIGHT = '8px';
export const DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID = 'devopsSegmentDeleteModal';
export const DATE_TIME_FORMAT = 'yyyy-mm-dd HH:MM';
......@@ -35,6 +37,10 @@ export const DEVOPS_ADOPTION_NO_RESULTS = s__('DevopsAdoption|No results…');
export const DEVOPS_ADOPTION_NO_SUB_GROUPS = s__('DevopsAdoption|This group has no sub-groups');
export const DEVOPS_ADOPTION_FEATURES_ADOPTED_TEXT = s__(
'DevopsAdoption|%{adoptedCount}/%{featuresCount} %{title} features adopted',
);
export const DEVOPS_ADOPTION_STRINGS = {
app: {
[DEVOPS_ADOPTION_ERROR_KEYS.groups]: s__(
......@@ -100,23 +106,20 @@ export const DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_DESC_STORAGE_KEY =
export const DEVOPS_ADOPTION_GROUP_COL_LABEL = __('Group');
export const DEVOPS_ADOPTION_OVERALL_CONFIGURATION = {
title: s__('DevopsAdoption|Overall adoption'),
icon: 'tanuki',
variant: 'primary',
cols: [],
};
export const DEVOPS_ADOPTION_TABLE_CONFIGURATION = [
{
title: s__('DevopsAdoption|Dev'),
tab: 'dev',
icon: 'code',
variant: 'warning',
cols: [
{
key: 'issueOpened',
label: s__('DevopsAdoption|Issues'),
tooltip: s__('DevopsAdoption|At least one issue opened'),
testId: 'issuesCol',
},
{
key: 'mergeRequestOpened',
label: s__('DevopsAdoption|MRs'),
tooltip: s__('DevopsAdoption|At least one MR opened'),
testId: 'mrsCol',
},
{
key: 'mergeRequestApproved',
label: s__('DevopsAdoption|Approvals'),
......@@ -129,11 +132,25 @@ export const DEVOPS_ADOPTION_TABLE_CONFIGURATION = [
tooltip: s__('DevopsAdoption|Code owners enabled for at least one project'),
testId: 'codeownersCol',
},
{
key: 'issueOpened',
label: s__('DevopsAdoption|Issues'),
tooltip: s__('DevopsAdoption|At least one issue opened'),
testId: 'issuesCol',
},
{
key: 'mergeRequestOpened',
label: s__('DevopsAdoption|MRs'),
tooltip: s__('DevopsAdoption|At least one MR opened'),
testId: 'mrsCol',
},
],
},
{
title: s__('DevopsAdoption|Sec'),
tab: 'sec',
icon: 'shield',
variant: 'info',
cols: [
{
key: 'securityScanSucceeded',
......@@ -146,12 +163,14 @@ export const DEVOPS_ADOPTION_TABLE_CONFIGURATION = [
{
title: s__('DevopsAdoption|Ops'),
tab: 'ops',
icon: 'rocket',
variant: 'success',
cols: [
{
key: 'runnerConfigured',
label: s__('DevopsAdoption|Runners'),
tooltip: s__('DevopsAdoption|Runner configured for project/group'),
testId: 'runnersCol',
key: 'deploySucceeded',
label: s__('DevopsAdoption|Deploys'),
tooltip: s__('DevopsAdoption|At least one deploy'),
testId: 'deploysCol',
},
{
key: 'pipelineSucceeded',
......@@ -160,10 +179,10 @@ export const DEVOPS_ADOPTION_TABLE_CONFIGURATION = [
testId: 'pipelinesCol',
},
{
key: 'deploySucceeded',
label: s__('DevopsAdoption|Deploys'),
tooltip: s__('DevopsAdoption|At least one deploy'),
testId: 'deploysCol',
key: 'runnerConfigured',
label: s__('DevopsAdoption|Runners'),
tooltip: s__('DevopsAdoption|Runner configured for project/group'),
testId: 'runnersCol',
},
],
},
......
......@@ -54,16 +54,16 @@ RSpec.describe 'DevOps Report page', :js do
visit admin_dev_ops_report_path
within tabs_selector do
expect(page.all(:css, tab_item_selector).length).to be(5)
expect(page).to have_text 'Dev Sec Ops DevOps Score'
expect(page.all(:css, tab_item_selector).length).to be(6)
expect(page).to have_text 'Overview Dev Sec Ops DevOps Score'
end
end
it 'defaults to the Dev tab' do
it 'defaults to the Overview tab' do
visit admin_dev_ops_report_path
within tabs_selector do
expect(page).to have_selector active_tab_selector, text: 'Dev'
expect(page).to have_selector active_tab_selector, text: 'Overview'
end
end
......@@ -83,10 +83,12 @@ RSpec.describe 'DevOps Report page', :js do
it_behaves_like 'displays tab content', tab[:text]
end
it 'does not add the tab param when the Dev tab is selected' do
it 'does not add the tab param when the Overview tab is selected' do
visit admin_dev_ops_report_path
click_link 'Dev'
within tabs_selector do
click_link 'Overview'
end
expect(page).to have_current_path(admin_dev_ops_report_path)
end
......
......@@ -5,6 +5,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import DevopsAdoptionAddDropdown from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_add_dropdown.vue';
import DevopsAdoptionApp from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_app.vue';
import DevopsAdoptionOverview from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_overview.vue';
import DevopsAdoptionSection from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_section.vue';
import {
DEVOPS_ADOPTION_STRINGS,
......@@ -101,6 +102,7 @@ describe('DevopsAdoptionApp', () => {
}
const findDevopsScoreTab = () => wrapper.findByTestId('devops-score-tab');
const findOverviewTab = () => wrapper.findByTestId('devops-overview-tab');
afterEach(() => {
wrapper.destroy();
......@@ -142,7 +144,7 @@ describe('DevopsAdoptionApp', () => {
});
it('displays the error message and calls Sentry', () => {
const alert = wrapper.find(GlAlert);
const alert = wrapper.findComponent(GlAlert);
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(DEVOPS_ADOPTION_STRINGS.app.groupsError);
expect(Sentry.captureException.mock.calls[0][0].networkError).toBe(error);
......@@ -239,11 +241,11 @@ describe('DevopsAdoptionApp', () => {
});
it('does not render the devops section', () => {
expect(wrapper.find(DevopsAdoptionSection).exists()).toBe(false);
expect(wrapper.findComponent(DevopsAdoptionSection).exists()).toBe(false);
});
it('displays the error message ', () => {
const alert = wrapper.find(GlAlert);
const alert = wrapper.findComponent(GlAlert);
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(DEVOPS_ADOPTION_STRINGS.app.addSegmentsError);
});
......@@ -270,11 +272,11 @@ describe('DevopsAdoptionApp', () => {
});
it('does not render the devops section', () => {
expect(wrapper.find(DevopsAdoptionSection).exists()).toBe(false);
expect(wrapper.findComponent(DevopsAdoptionSection).exists()).toBe(false);
});
it('displays the error message ', () => {
const alert = wrapper.find(GlAlert);
const alert = wrapper.findComponent(GlAlert);
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(DEVOPS_ADOPTION_STRINGS.app.segmentsError);
});
......@@ -344,6 +346,16 @@ describe('DevopsAdoptionApp', () => {
};
const defaultDevopsAdoptionTabBehavior = () => {
describe('overview tab', () => {
it('displays the overview tab', () => {
expect(findOverviewTab().exists()).toBe(true);
});
it('displays the devops adoption overview component', () => {
expect(findOverviewTab().findComponent(DevopsAdoptionOverview).exists()).toBe(true);
});
});
describe('devops adoption tabs', () => {
it('displays the configured number of tabs', () => {
expect(wrapper.findAllByTestId('devops-adoption-tab')).toHaveLength(
......@@ -353,12 +365,15 @@ describe('DevopsAdoptionApp', () => {
it('displays the devops section component with the tab', () => {
expect(
wrapper.findByTestId('devops-adoption-tab').find(DevopsAdoptionSection).exists(),
wrapper
.findByTestId('devops-adoption-tab')
.findComponent(DevopsAdoptionSection)
.exists(),
).toBe(true);
});
it('displays the DevopsAdoptionAddDropdown as the last tab', () => {
expect(wrapper.find(DevopsAdoptionAddDropdown).exists()).toBe(true);
expect(wrapper.findComponent(DevopsAdoptionAddDropdown).exists()).toBe(true);
});
eventTrackingBehaviour('devops-adoption-tab', 'i_analytics_dev_ops_adoption');
......@@ -379,7 +394,7 @@ describe('DevopsAdoptionApp', () => {
});
it('displays the devops score component', () => {
expect(findDevopsScoreTab().find(DevopsScore).exists()).toBe(true);
expect(findDevopsScoreTab().findComponent(DevopsScore).exists()).toBe(true);
});
eventTrackingBehaviour('devops-score-tab', 'i_analytics_dev_ops_score');
......
import { GlIcon, GlProgressBar } from '@gitlab/ui';
import DevopsAdoptionOverviewCard from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_overview_card.vue';
import DevopsAdoptionTableCellFlag from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_table_cell_flag.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { overallAdoptionData } from '../mock_data';
describe('DevopsAdoptionOverview', () => {
let wrapper;
const createComponent = (props) => {
wrapper = shallowMountExtended(DevopsAdoptionOverviewCard, {
propsData: {
...overallAdoptionData,
displayMeta: true,
...props,
},
});
};
describe('default state', () => {
beforeEach(() => {
createComponent();
});
describe('title', () => {
it('displays a icon', () => {
const icon = wrapper.findComponent(GlIcon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe(overallAdoptionData.icon);
});
it('displays the title text', () => {
const text = wrapper.findByTestId('card-title-text');
expect(text.exists()).toBe(true);
expect(text.text()).toBe(overallAdoptionData.title);
});
});
it('displays the progress bar', () => {
expect(wrapper.findComponent(GlProgressBar).exists()).toBe(true);
});
it('displays the description correctly', () => {
const text = wrapper.findByTestId('card-description');
expect(text.exists()).toBe(true);
expect(text.text()).toBe('3/8 Overall adoption features adopted');
});
describe('meta', () => {
it('displays the meta', () => {
expect(wrapper.findByTestId('card-meta-row').exists()).toBe(true);
});
it('displays the correct number of rows', () => {
expect(wrapper.findAllByTestId('card-meta-row')).toHaveLength(
overallAdoptionData.featureMeta.length,
);
});
describe('meta row', () => {
it('displays a cell flag component', () => {
expect(wrapper.findComponent(DevopsAdoptionTableCellFlag).exists()).toBe(true);
});
it('displays the feature title', () => {
expect(wrapper.findByTestId('card-meta-row-title').text()).toBe(
overallAdoptionData.featureMeta[0].title,
);
});
});
});
});
describe('when not displaying meta', () => {
beforeEach(() => {
createComponent({ displayMeta: false });
});
it('displays the description correctly', () => {
const text = wrapper.findByTestId('card-description');
expect(text.exists()).toBe(true);
expect(text.text()).toBe('3/8 features adopted');
});
it('does not display the meta', () => {
expect(wrapper.findByTestId('card-meta-row').exists()).toBe(false);
});
});
});
import { GlLoadingIcon } from '@gitlab/ui';
import DevopsAdoptionOverview from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_overview.vue';
import DevopsAdoptionOverviewCard from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_overview_card.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { devopsAdoptionNamespaceData, overallAdoptionData } from '../mock_data';
describe('DevopsAdoptionOverview', () => {
let wrapper;
const createComponent = (props) => {
wrapper = shallowMountExtended(DevopsAdoptionOverview, {
propsData: {
timestamp: '2020-10-31 23:59',
data: devopsAdoptionNamespaceData,
...props,
},
});
};
describe('default state', () => {
beforeEach(() => {
createComponent();
});
it('displays the overview container', () => {
expect(wrapper.findByTestId('overview-container').exists()).toBe(true);
});
describe('overview container', () => {
it('displays the header text', () => {
const text = wrapper.findByTestId('overview-container-header');
expect(text.exists()).toBe(true);
expect(text.text()).toBe(
'Feature adoption is based on usage in the previous calendar month. Last updated: 2020-10-31 23:59.',
);
});
it('displays the correct numnber of overview cards', () => {
expect(wrapper.findAllComponents(DevopsAdoptionOverviewCard)).toHaveLength(4);
});
it('passes the cards the correct data', () => {
expect(wrapper.findComponent(DevopsAdoptionOverviewCard).props()).toStrictEqual(
overallAdoptionData,
);
});
});
});
describe('loading', () => {
beforeEach(() => {
createComponent({ loading: true });
});
it('displays a loading icon', () => {
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('does not display the overview container', () => {
expect(wrapper.findByTestId('overview-container').exists()).toBe(false);
});
});
});
......@@ -234,7 +234,7 @@ describe('DevopsAdoptionTable', () => {
await nextTick();
expect(findSortByLocalStorageSync().props('value')).toBe('issueOpened');
expect(findSortByLocalStorageSync().props('value')).toBe('mergeRequestApproved');
});
it('should update local storage when the sort direction changes', async () => {
......
......@@ -77,23 +77,23 @@ export const devopsAdoptionTableHeaders = [
},
{
index: 1,
label: 'Issues',
tooltip: 'At least one issue opened',
label: 'Approvals',
tooltip: 'At least one approval on an MR',
},
{
index: 2,
label: 'MRs',
tooltip: 'At least one MR opened',
label: 'Code owners',
tooltip: 'Code owners enabled for at least one project',
},
{
index: 3,
label: 'Approvals',
tooltip: 'At least one approval on an MR',
label: 'Issues',
tooltip: 'At least one issue opened',
},
{
index: 4,
label: 'Code owners',
tooltip: 'Code owners enabled for at least one project',
label: 'MRs',
tooltip: 'At least one MR opened',
},
{
index: 5,
......@@ -110,3 +110,44 @@ export const dataErrorMessage = 'Name already taken.';
export const genericDeleteErrorMessage =
'An error occurred while removing the group. Please try again.';
export const overallAdoptionData = {
displayMeta: false,
featureMeta: [
{
adopted: false,
title: 'Approvals',
},
{
adopted: false,
title: 'Code owners',
},
{
adopted: true,
title: 'Issues',
},
{
adopted: true,
title: 'MRs',
},
{
adopted: false,
title: 'Scanning',
},
{
adopted: false,
title: 'Deploys',
},
{
adopted: false,
title: 'Pipelines',
},
{
adopted: true,
title: 'Runners',
},
],
icon: 'tanuki',
title: 'Overall adoption',
variant: 'primary',
};
......@@ -11072,6 +11072,9 @@ msgstr ""
msgid "DevOps adoption"
msgstr ""
msgid "DevopsAdoption|%{adoptedCount}/%{featuresCount} %{title} features adopted"
msgstr ""
msgid "DevopsAdoption|Add Group"
msgstr ""
......@@ -11174,6 +11177,9 @@ msgstr ""
msgid "DevopsAdoption|Ops"
msgstr ""
msgid "DevopsAdoption|Overall adoption"
msgstr ""
msgid "DevopsAdoption|Pipelines"
msgstr ""
......@@ -11231,6 +11237,9 @@ msgstr ""
msgid "DevopsReport|Moderate"
msgstr ""
msgid "DevopsReport|Overview"
msgstr ""
msgid "DevopsReport|Score"
msgstr ""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment