Commit 2c2acc53 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'nfriend-hide-group-dora-charts-if-not' into 'master'

Restrict visibility of group-level DORA charts to Ultimate users

See merge request gitlab-org/gitlab!65478
parents 83a34286 67274738
...@@ -3,7 +3,6 @@ import { GlTabs, GlTab } from '@gitlab/ui'; ...@@ -3,7 +3,6 @@ import { GlTabs, GlTab } from '@gitlab/ui';
import DeploymentFrequencyCharts from 'ee/dora/components/deployment_frequency_charts.vue'; import DeploymentFrequencyCharts from 'ee/dora/components/deployment_frequency_charts.vue';
import LeadTimeCharts from 'ee/dora/components/lead_time_charts.vue'; import LeadTimeCharts from 'ee/dora/components/lead_time_charts.vue';
import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility'; import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility';
import { TABS } from '../constants';
import ReleaseStatsCard from './release_stats_card.vue'; import ReleaseStatsCard from './release_stats_card.vue';
export default { export default {
...@@ -15,11 +14,31 @@ export default { ...@@ -15,11 +14,31 @@ export default {
DeploymentFrequencyCharts, DeploymentFrequencyCharts,
LeadTimeCharts, LeadTimeCharts,
}, },
inject: {
shouldRenderDoraCharts: {
type: Boolean,
default: false,
},
},
data() { data() {
return { return {
selectedTabIndex: 0, selectedTabIndex: 0,
}; };
}, },
computed: {
tabs() {
const tabsToShow = ['release-statistics'];
if (this.shouldRenderDoraCharts) {
tabsToShow.push('deployment-frequency', 'lead-time');
}
return tabsToShow;
},
releaseStatsCardClasses() {
return ['gl-mt-5'];
},
},
created() { created() {
this.selectTab(); this.selectTab();
window.addEventListener('popstate', this.selectTab); window.addEventListener('popstate', this.selectTab);
...@@ -27,13 +46,13 @@ export default { ...@@ -27,13 +46,13 @@ export default {
methods: { methods: {
selectTab() { selectTab() {
const [tabQueryParam] = getParameterValues('tab'); const [tabQueryParam] = getParameterValues('tab');
const tabIndex = TABS.indexOf(tabQueryParam); const tabIndex = this.tabs.indexOf(tabQueryParam);
this.selectedTabIndex = tabIndex >= 0 ? tabIndex : 0; this.selectedTabIndex = tabIndex >= 0 ? tabIndex : 0;
}, },
onTabChange(newIndex) { onTabChange(newIndex) {
if (newIndex !== this.selectedTabIndex) { if (newIndex !== this.selectedTabIndex) {
this.selectedTabIndex = newIndex; this.selectedTabIndex = newIndex;
const path = mergeUrlParams({ tab: TABS[newIndex] }, window.location.pathname); const path = mergeUrlParams({ tab: this.tabs[newIndex] }, window.location.pathname);
updateHistory({ url: path, title: window.title }); updateHistory({ url: path, title: window.title });
} }
}, },
...@@ -42,16 +61,19 @@ export default { ...@@ -42,16 +61,19 @@ export default {
</script> </script>
<template> <template>
<div> <div>
<gl-tabs :value="selectedTabIndex" @input="onTabChange"> <gl-tabs v-if="tabs.length > 1" :value="selectedTabIndex" @input="onTabChange">
<gl-tab :title="s__('CICDAnalytics|Release statistics')"> <gl-tab :title="s__('CICDAnalytics|Release statistics')">
<release-stats-card class="gl-mt-5" /> <release-stats-card :class="releaseStatsCardClasses" />
</gl-tab>
<gl-tab :title="s__('CICDAnalytics|Deployment frequency')">
<deployment-frequency-charts />
</gl-tab>
<gl-tab :title="s__('CICDAnalytics|Lead time')">
<lead-time-charts />
</gl-tab> </gl-tab>
<template v-if="shouldRenderDoraCharts">
<gl-tab :title="s__('CICDAnalytics|Deployment frequency')">
<deployment-frequency-charts />
</gl-tab>
<gl-tab :title="s__('CICDAnalytics|Lead time')">
<lead-time-charts />
</gl-tab>
</template>
</gl-tabs> </gl-tabs>
<release-stats-card v-else :class="releaseStatsCardClasses" />
</div> </div>
</template> </template>
export const STAT_ERROR_PLACEHOLDER = '-'; export const STAT_ERROR_PLACEHOLDER = '-';
export const TABS = ['release-statistics', 'deployment-frequency', 'lead-time'];
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import CiCdAnalyticsApp from './components/app.vue'; import CiCdAnalyticsApp from './components/app.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -16,11 +17,14 @@ export default () => { ...@@ -16,11 +17,14 @@ export default () => {
const { fullPath } = el.dataset; const { fullPath } = el.dataset;
const shouldRenderDoraCharts = parseBoolean(el.dataset.shouldRenderDoraCharts);
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
provide: { provide: {
groupPath: fullPath, groupPath: fullPath,
shouldRenderDoraCharts,
}, },
render: (createElement) => createElement(CiCdAnalyticsApp), render: (createElement) => createElement(CiCdAnalyticsApp),
}); });
......
...@@ -6,9 +6,11 @@ module EE ...@@ -6,9 +6,11 @@ module EE
override :should_render_dora_charts override :should_render_dora_charts
def should_render_dora_charts def should_render_dora_charts
return false unless @project.feature_available?(:dora4_analytics) container = @project || @group
can?(current_user, :read_dora4_analytics, @project) return false unless container.feature_available?(:dora4_analytics)
can?(current_user, :read_dora4_analytics, container)
end end
end end
end end
- page_title _("CI/CD Analytics") - page_title _("CI/CD Analytics")
#js-group-ci-cd-analytics-app{ data: { full_path: @group.full_path } } #js-group-ci-cd-analytics-app{ data: { full_path: @group.full_path,
should_render_dora_charts: should_render_dora_charts.to_s } }
...@@ -4,7 +4,7 @@ require 'spec_helper' ...@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe 'Group CI/CD Analytics', :js do RSpec.describe 'Group CI/CD Analytics', :js do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) } let_it_be_with_refind(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group ) } let_it_be(:subgroup) { create(:group, parent: group ) }
let_it_be(:project_1) { create(:project, group: group) } let_it_be(:project_1) { create(:project, group: group) }
let_it_be(:project_2) { create(:project, group: group) } let_it_be(:project_2) { create(:project, group: group) }
...@@ -15,7 +15,7 @@ RSpec.describe 'Group CI/CD Analytics', :js do ...@@ -15,7 +15,7 @@ RSpec.describe 'Group CI/CD Analytics', :js do
let_it_be(:unrelated_release) { create(:release, project: unrelated_project) } let_it_be(:unrelated_release) { create(:release, project: unrelated_project) }
before do before do
stub_licensed_features(group_ci_cd_analytics: true) stub_licensed_features(group_ci_cd_analytics: true, dora4_analytics: true)
group.add_reporter(user) group.add_reporter(user)
sign_in(user) sign_in(user)
visit group_analytics_ci_cd_analytics_path(group) visit group_analytics_ci_cd_analytics_path(group)
......
import { GlTabs, GlTab } from '@gitlab/ui'; import { GlTabs, GlTab } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { merge } from 'lodash';
import CiCdAnalyticsApp from 'ee/analytics/group_ci_cd_analytics/components/app.vue'; import CiCdAnalyticsApp from 'ee/analytics/group_ci_cd_analytics/components/app.vue';
import ReleaseStatsCard from 'ee/analytics/group_ci_cd_analytics/components/release_stats_card.vue'; import ReleaseStatsCard from 'ee/analytics/group_ci_cd_analytics/components/release_stats_card.vue';
import DeploymentFrequencyCharts from 'ee/dora/components/deployment_frequency_charts.vue'; import DeploymentFrequencyCharts from 'ee/dora/components/deployment_frequency_charts.vue';
...@@ -17,8 +18,18 @@ describe('ee/analytics/group_ci_cd_analytics/components/app.vue', () => { ...@@ -17,8 +18,18 @@ describe('ee/analytics/group_ci_cd_analytics/components/app.vue', () => {
getParameterValues.mockReturnValue([]); getParameterValues.mockReturnValue([]);
}); });
const createComponent = () => { const createComponent = (mountOptions = {}) => {
wrapper = shallowMount(CiCdAnalyticsApp); wrapper = shallowMount(
CiCdAnalyticsApp,
merge(
{
provide: {
shouldRenderDoraCharts: true,
},
},
mountOptions,
),
);
}; };
const findGlTabs = () => wrapper.findComponent(GlTabs); const findGlTabs = () => wrapper.findComponent(GlTabs);
...@@ -26,15 +37,32 @@ describe('ee/analytics/group_ci_cd_analytics/components/app.vue', () => { ...@@ -26,15 +37,32 @@ describe('ee/analytics/group_ci_cd_analytics/components/app.vue', () => {
const findGlTabAtIndex = (index) => findAllGlTabs().at(index); const findGlTabAtIndex = (index) => findAllGlTabs().at(index);
describe('tabs', () => { describe('tabs', () => {
beforeEach(() => { describe('when the DORA charts are available', () => {
createComponent(); beforeEach(() => {
createComponent();
});
it('renders tabs in the correct order', () => {
expect(findGlTabs().exists()).toBe(true);
expect(findAllGlTabs().length).toBe(3);
expect(findGlTabAtIndex(0).attributes('title')).toBe('Release statistics');
expect(findGlTabAtIndex(1).attributes('title')).toBe('Deployment frequency');
expect(findGlTabAtIndex(2).attributes('title')).toBe('Lead time');
});
}); });
it('renders tabs in the correct order', () => { describe('when the DORA charts are not available', () => {
expect(findGlTabs().exists()).toBe(true); beforeEach(() => {
createComponent({ provide: { shouldRenderDoraCharts: false } });
});
it('does not render any tabs', () => {
expect(findGlTabs().exists()).toBe(false);
});
expect(findGlTabAtIndex(0).attributes('title')).toBe('Release statistics'); it('renders the release statistics component', () => {
expect(findGlTabAtIndex(1).attributes('title')).toBe('Deployment frequency'); expect(wrapper.findComponent(ReleaseStatsCard).exists()).toBe(true);
});
}); });
}); });
......
...@@ -4,16 +4,15 @@ require 'spec_helper' ...@@ -4,16 +4,15 @@ require 'spec_helper'
RSpec.describe EE::GraphHelper do RSpec.describe EE::GraphHelper do
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be_with_refind(:group) { create(:group) }
let_it_be(:project) { create(:project, :private) }
let(:project) { create(:project, :private) }
let(:is_feature_licensed) { true } let(:is_feature_licensed) { true }
let(:is_user_authorized) { true } let(:is_user_authorized) { true }
before do before do
stub_licensed_features(dora4_analytics: is_feature_licensed) stub_licensed_features(dora4_analytics: is_feature_licensed)
self.instance_variable_set(:@current_user, current_user) self.instance_variable_set(:@current_user, current_user)
self.instance_variable_set(:@project, project)
allow(self).to receive(:can?).with(current_user, :read_dora4_analytics, project).and_return(is_user_authorized)
end end
describe '#should_render_dora_charts' do describe '#should_render_dora_charts' do
...@@ -25,18 +24,38 @@ RSpec.describe EE::GraphHelper do ...@@ -25,18 +24,38 @@ RSpec.describe EE::GraphHelper do
it { expect(should_render_dora_charts).to be(false) } it { expect(should_render_dora_charts).to be(false) }
end end
it_behaves_like 'returns true' shared_examples '#should_render_dora_charts for a specific type of container' do
it_behaves_like 'returns true'
context 'when the feature is not available' do context 'when the feature is not available' do
let(:is_feature_licensed) { false } let(:is_feature_licensed) { false }
it_behaves_like 'returns false' it_behaves_like 'returns false'
end
context 'when the user does not have permission' do
let(:is_user_authorized) { false }
it_behaves_like 'returns false'
end
end
context 'when serving the project-level DORA page' do
before do
self.instance_variable_set(:@project, project)
allow(self).to receive(:can?).with(current_user, :read_dora4_analytics, project).and_return(is_user_authorized)
end
it_behaves_like '#should_render_dora_charts for a specific type of container'
end end
context 'when the user does not have permission' do context 'when serving the group-level DORA page' do
let(:is_user_authorized) { false } before do
self.instance_variable_set(:@group, group)
allow(self).to receive(:can?).with(current_user, :read_dora4_analytics, group).and_return(is_user_authorized)
end
it_behaves_like 'returns false' it_behaves_like '#should_render_dora_charts for a specific type of container'
end end
end end
end end
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