Commit a357dc82 authored by Simon Knox's avatar Simon Knox Committed by Heinrich Lee Yu

[Feature flag] Remove burnup_charts and iteration_charts

parent c0f7c071
......@@ -5,9 +5,6 @@ class Groups::MilestonesController < Groups::ApplicationController
before_action :milestone, only: [:edit, :show, :update, :issues, :merge_requests, :participants, :labels, :destroy]
before_action :authorize_admin_milestones!, only: [:edit, :new, :create, :update, :destroy]
before_action do
push_frontend_feature_flag(:burnup_charts, @group, default_enabled: true)
end
feature_category :issue_tracking
......
......@@ -6,9 +6,6 @@ class Projects::MilestonesController < Projects::ApplicationController
before_action :check_issuables_available!
before_action :milestone, only: [:edit, :update, :destroy, :show, :issues, :merge_requests, :participants, :labels, :promote]
before_action do
push_frontend_feature_flag(:burnup_charts, @project, default_enabled: true)
end
# Allow read any milestone
before_action :authorize_read_milestone!
......
---
name: burnup_charts
introduced_by_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/268350
milestone: '13.6'
type: development
group: group::project management
default_enabled: true
......@@ -76,12 +76,7 @@ To view an iteration report, go to the iterations list page and click an iterati
### Iteration burndown and burnup charts
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/222750) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.5.
> - It was deployed behind a feature flag, disabled by default.
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45492) on GitLab 13.6.
> - It's enabled on GitLab.com.
> - It's able to be enabled or disabled per-group.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-iteration-charts). **(STARTER ONLY)**
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/269972) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.7.
The iteration report includes [burndown and burnup charts](../../project/milestones/burndown_and_burnup_charts.md),
similar to how they appear when viewing a [milestone](../../project/milestones/index.md).
......@@ -113,30 +108,6 @@ Feature.disable(:group_iterations)
Feature.disable(:group_iterations, Group.find(<group ID>))
```
## Disable iteration charts **(STARTER ONLY)**
GitLab iteration charts feature is deployed with a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can disable it for your instance. `:iteration_charts` can be enabled or disabled per-group.
To enable it:
```ruby
# Instance-wide
Feature.enable(:iteration_charts)
# or by group
Feature.enable(:iteration_charts, Group.find(<group ID>))
```
To disable it:
```ruby
# Instance-wide
Feature.disable(:iteration_charts)
# or by group
Feature.disable(:iteration_charts, Group.find(<group ID>))
```
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
......
......@@ -104,13 +104,7 @@ Reopened issues are considered as having been opened on the day after they were
## Burnup charts
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/6903) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.6.
> - It's [deployed behind a feature flag](../../feature_flags.md), enabled by default.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-burnup-charts). **(STARTER ONLY)**
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/268350) in [GitLab Starter](https://about.gitlab.com/pricing/) 13.7.
Burnup charts show the assigned and completed work for a milestone.
......@@ -136,25 +130,6 @@ Burnup charts can show either the total number of issues or total weight for eac
day of the milestone. Use the toggle above the charts to switch between total
and weight.
### Enable or disable burnup charts **(STARTER ONLY)**
Burnup charts is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to disable it.
To enable it:
```ruby
Feature.enable(:burnup_charts)
```
To disable it:
```ruby
Feature.disable(:burnup_charts)
```
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
......
......@@ -54,7 +54,7 @@ export default {
apollo: {
burnupData: {
skip() {
return !this.glFeatures.burnupCharts || (!this.milestoneId && !this.iterationId);
return !this.milestoneId && !this.iterationId;
},
query: BurnupQuery,
variables() {
......@@ -79,7 +79,7 @@ export default {
openIssuesWeight: [],
issuesSelected: true,
burnupData: [],
useLegacyBurndown: !this.glFeatures.burnupCharts,
useLegacyBurndown: false,
showInfo: this.showNewOldBurndownToggle,
error: '',
};
......@@ -89,7 +89,7 @@ export default {
return this.iterationId ? 'iteration' : 'milestone';
},
title() {
return this.glFeatures.burnupCharts ? __('Charts') : __('Burndown chart');
return __('Charts');
},
issueButtonCategory() {
return this.issuesSelected ? 'primary' : 'secondary';
......@@ -110,11 +110,6 @@ export default {
return this.pluckBurnupDataProperties('scopeWeight', 'completedWeight');
},
},
mounted() {
if (!this.glFeatures.burnupCharts) {
this.fetchLegacyBurndownEvents();
}
},
methods: {
fetchLegacyBurndownEvents() {
this.fetchedLegacyData = true;
......@@ -230,12 +225,7 @@ export default {
<template>
<div>
<gl-alert
v-if="glFeatures.burnupCharts && showInfo"
variant="info"
class="col-12 gl-mt-3"
@dismiss="showInfo = false"
>
<gl-alert v-if="showInfo" variant="info" class="col-12 gl-mt-3" @dismiss="showInfo = false">
<gl-sprintf
:message="
__(
......@@ -272,7 +262,7 @@ export default {
</gl-button>
</gl-button-group>
<gl-button-group v-if="glFeatures.burnupCharts && showNewOldBurndownToggle">
<gl-button-group v-if="showNewOldBurndownToggle">
<gl-button
ref="oldBurndown"
:category="useLegacyBurndown ? 'primary' : 'secondary'"
......@@ -293,7 +283,7 @@ export default {
</gl-button>
</gl-button-group>
</div>
<div v-if="glFeatures.burnupCharts" class="row">
<div class="row">
<gl-alert v-if="error" variant="danger" class="col-12" @dismiss="error = ''">
{{ error }}
</gl-alert>
......@@ -313,14 +303,5 @@ export default {
class="col-md-6"
/>
</div>
<burndown-chart
v-else
:show-title="false"
:start-date="startDate"
:due-date="dueDate"
:open-issues-count="openIssuesCount"
:open-issues-weight="openIssuesWeight"
:issues-selected="issuesSelected"
/>
</div>
</template>
......@@ -220,7 +220,6 @@ export default {
:namespace-type="namespaceType"
/>
<burn-charts
v-if="glFeatures.iterationCharts && glFeatures.burnupCharts"
:start-date="iteration.startDate"
:due-date="iteration.dueDate"
:iteration-id="iteration.id"
......
......@@ -4,10 +4,6 @@ class Groups::IterationsController < Groups::ApplicationController
before_action :check_iterations_available!
before_action :authorize_show_iteration!, only: [:index, :show]
before_action :authorize_create_iteration!, only: [:new, :edit]
before_action do
push_frontend_feature_flag(:iteration_charts, group, default_enabled: true)
push_frontend_feature_flag(:burnup_charts, group, default_enabled: true)
end
feature_category :issue_tracking
......
......@@ -3,10 +3,6 @@
class Projects::Iterations::InheritedController < Projects::ApplicationController
before_action :check_iterations_available!
before_action :authorize_show_iteration!
before_action do
push_frontend_feature_flag(:iteration_charts, project, default_enabled: true)
push_frontend_feature_flag(:burnup_charts, project, default_enabled: true)
end
feature_category :issue_tracking
......
......@@ -3,10 +3,6 @@
class Projects::IterationsController < Projects::ApplicationController
before_action :check_iterations_available!
before_action :authorize_show_iteration!
before_action do
push_frontend_feature_flag(:iteration_charts, project, default_enabled: true)
push_frontend_feature_flag(:burnup_charts, project, default_enabled: true)
end
feature_category :issue_tracking
......
......@@ -7,8 +7,6 @@ module Resolvers
alias_method :timebox, :synchronized_object
def resolve(*args)
return {} unless timebox.burnup_charts_available?
response = TimeboxReportService.new(timebox).execute
raise GraphQL::ExecutionError, response.message if response.error?
......
......@@ -46,10 +46,6 @@ module EE
resource_parent&.feature_available?(:iterations) && weight_available?
end
def burnup_charts_available?
::Feature.enabled?(:iteration_charts, resource_parent, default_enabled: true)
end
private
def timebox_format_reference(format = :id)
......
......@@ -15,9 +15,5 @@ module EE
end
alias_method :supports_timebox_charts?, :supports_milestone_charts?
def burnup_charts_available?
::Feature.enabled?(:burnup_charts, resource_parent, default_enabled: true)
end
end
end
---
name: iteration_charts
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41280
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/229046
milestone: '13.4'
type: development
group: group::project management
default_enabled: true
......@@ -52,20 +52,6 @@ RSpec.describe 'Burnup charts', :js do
end
end
describe 'feature flag disabled' do
before do
stub_licensed_features(milestone_charts: true)
stub_feature_flags(burnup_charts: false)
end
it 'only shows burndown chart' do
visit group_milestone_path(milestone.group, milestone)
expect(page).to have_selector(burndown_chart_selector)
expect(page).not_to have_selector(burnup_chart_selector)
end
end
def burnup_chart_points
fill_color = "#5772ff"
burnup_chart.all("path[fill='#{fill_color}']", count: 12)
......
......@@ -6,8 +6,7 @@ import BurnCharts from 'ee/burndown_chart/components/burn_charts.vue';
import BurndownChart from 'ee/burndown_chart/components/burndown_chart.vue';
import BurnupChart from 'ee/burndown_chart/components/burnup_chart.vue';
import { useFakeDate } from 'helpers/fake_date';
import waitForPromises from 'helpers/wait_for_promises';
import { day1, day2, day3, day4, legacyBurndownEvents } from '../mock_data';
import { day1, day2, day3, day4 } from '../mock_data';
function fakeDate({ date }) {
const [year, month, day] = date.split('-');
......@@ -37,7 +36,7 @@ describe('burndown_chart', () => {
burndownEventsPath: '/api/v4/projects/1234/milestones/1/burndown_events',
};
const createComponent = ({ props = {}, featureEnabled = false, data = {} } = {}) => {
const createComponent = ({ props = {}, data = {} } = {}) => {
wrapper = shallowMount(BurnCharts, {
propsData: {
...defaultProps,
......@@ -46,9 +45,6 @@ describe('burndown_chart', () => {
data() {
return data;
},
provide: {
glFeatures: { burnupCharts: featureEnabled },
},
});
};
......@@ -58,6 +54,7 @@ describe('burndown_chart', () => {
afterEach(() => {
mock.restore();
wrapper.destroy();
});
it('includes Issues and Issue weight buttons', () => {
......@@ -95,136 +92,61 @@ describe('burndown_chart', () => {
expect(findBurndownChart().props('issuesSelected')).toBe(false);
});
describe('feature disabled', () => {
beforeEach(() => {
fakeDate(day4);
mock.onGet(defaultProps.burndownEventsPath).reply(200, legacyBurndownEvents);
createComponent({ featureEnabled: false });
});
it('calls fetchLegacyBurndownEvents when mounted', async () => {
await waitForPromises();
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
expect(findBurndownChart().props().openIssuesCount).toEqual([
[defaultProps.startDate, 0],
[day1.date, 1],
[day2.date, 2],
[day3.date, 3],
[day4.date, 2],
]);
expect(findBurndownChart().props().openIssuesWeight).toEqual([
[defaultProps.startDate, 0],
[day1.date, 2],
[day2.date, 3],
[day3.date, 4],
[day4.date, 2],
]);
});
it('does not reduce width of burndown chart', () => {
expect(findBurndownChart().classes()).toEqual([]);
});
it('sets section title and chart title correctly', () => {
expect(findChartsTitle().text()).toBe('Burndown chart');
expect(findBurndownChart().props().showTitle).toBe(false);
});
it('does not show old/new burndown buttons', () => {
expect(findOldBurndownChartButton().exists()).toBe(false);
expect(findNewBurndownChartButton().exists()).toBe(false);
});
it('uses count and weight from data', () => {
const expectedCount = [day2.date, day2.scopeCount];
const expectedWeight = [day2.date, day2.scopeWeight];
it('reduces width of burndown chart', () => {
createComponent();
createComponent({
data: {
burnupData: [day1],
openIssuesCount: [expectedCount],
openIssuesWeight: [expectedWeight],
},
props: {
milestoneId: '1234',
},
featureEnabled: false,
});
expect(findBurndownChart().classes()).toContain('col-md-6');
});
const { openIssuesCount, openIssuesWeight } = findBurndownChart().props();
it('sets section title and chart title correctly', () => {
createComponent();
expect(openIssuesCount).toEqual([expectedCount]);
expect(openIssuesWeight).toEqual([expectedWeight]);
});
expect(findChartsTitle().text()).toBe('Charts');
expect(findBurndownChart().props().showTitle).toBe(true);
});
describe('feature enabled', () => {
beforeEach(() => {
createComponent({ featureEnabled: true });
});
it('reduces width of burndown chart', () => {
expect(findBurndownChart().classes()).toContain('col-md-6');
});
it('sets weight prop of burnup chart', async () => {
createComponent();
it('sets section title and chart title correctly', () => {
expect(findChartsTitle().text()).toBe('Charts');
expect(findBurndownChart().props().showTitle).toBe(true);
});
findWeightButton().vm.$emit('click');
it('sets weight prop of burnup chart', async () => {
findWeightButton().vm.$emit('click');
await wrapper.vm.$nextTick();
await wrapper.vm.$nextTick();
expect(findBurnupChart().props('issuesSelected')).toBe(false);
});
expect(findBurnupChart().props('issuesSelected')).toBe(false);
it('uses burndown data computed from burnup data', () => {
createComponent({
data: {
burnupData: [day1],
},
});
const { openIssuesCount, openIssuesWeight } = findBurndownChart().props();
it('uses burndown data computed from burnup data', () => {
createComponent({
data: {
burnupData: [day1],
},
featureEnabled: true,
});
const { openIssuesCount, openIssuesWeight } = findBurndownChart().props();
const expectedCount = [day1.date, day1.scopeCount - day1.completedCount];
const expectedWeight = [day1.date, day1.scopeWeight - day1.completedWeight];
const expectedCount = [day1.date, day1.scopeCount - day1.completedCount];
const expectedWeight = [day1.date, day1.scopeWeight - day1.completedWeight];
expect(openIssuesCount).toEqual([expectedCount]);
expect(openIssuesWeight).toEqual([expectedWeight]);
});
expect(openIssuesCount).toEqual([expectedCount]);
expect(openIssuesWeight).toEqual([expectedWeight]);
});
describe('showNewOldBurndownToggle', () => {
it('hides old/new burndown buttons if feature disabled', () => {
createComponent({ featureEnabled: false, props: { showNewOldBurndownToggle: true } });
expect(findOldBurndownChartButton().exists()).toBe(false);
expect(findNewBurndownChartButton().exists()).toBe(false);
});
it('hides old/new burndown buttons if props is false', () => {
createComponent({ featureEnabled: true, props: { showNewOldBurndownToggle: false } });
createComponent({ props: { showNewOldBurndownToggle: false } });
expect(findOldBurndownChartButton().exists()).toBe(false);
expect(findNewBurndownChartButton().exists()).toBe(false);
});
it('shows old/new burndown buttons if prop true', () => {
createComponent({ featureEnabled: true, props: { showNewOldBurndownToggle: true } });
createComponent({ props: { showNewOldBurndownToggle: true } });
expect(findOldBurndownChartButton().exists()).toBe(true);
expect(findNewBurndownChartButton().exists()).toBe(true);
});
it('calls fetchLegacyBurndownEvents, but only once', () => {
createComponent({ featureEnabled: true, props: { showNewOldBurndownToggle: true } });
createComponent({ props: { showNewOldBurndownToggle: true } });
jest.spyOn(wrapper.vm, 'fetchLegacyBurndownEvents');
mock.onGet(defaultProps.burndownEventsPath).reply(200, []);
......@@ -239,7 +161,6 @@ describe('burndown_chart', () => {
beforeEach(() => {
createComponent({
props: { startDate: day1.date, dueDate: day4.date },
featureEnabled: true,
});
fakeDate(day4);
......
......@@ -18,54 +18,38 @@ RSpec.describe Resolvers::TimeboxReportResolver do
RSpec.shared_examples 'timebox time series' do
subject { resolve(described_class, obj: timebox) }
context 'when the feature flag is disabled' do
before do
stub_feature_flags(burnup_charts: false, iteration_charts: false)
end
it 'returns empty data' do
expect(subject).to be_empty
end
it 'returns burnup chart data' do
expect(subject).to eq(
stats: {
complete: { count: 0, weight: 0 },
incomplete: { count: 2, weight: 0 },
total: { count: 2, weight: 0 }
},
burnup_time_series: [
{
date: start_date + 4.days,
scope_count: 1,
scope_weight: 0,
completed_count: 0,
completed_weight: 0
},
{
date: start_date + 9.days,
scope_count: 2,
scope_weight: 0,
completed_count: 0,
completed_weight: 0
}
])
end
context 'when the feature flag is enabled' do
context 'when the service returns an error' do
before do
stub_feature_flags(burnup_charts: true, iteration_charts: true)
stub_const('TimeboxReportService::EVENT_COUNT_LIMIT', 1)
end
it 'returns burnup chart data' do
expect(subject).to eq(
stats: {
complete: { count: 0, weight: 0 },
incomplete: { count: 2, weight: 0 },
total: { count: 2, weight: 0 }
},
burnup_time_series: [
{
date: start_date + 4.days,
scope_count: 1,
scope_weight: 0,
completed_count: 0,
completed_weight: 0
},
{
date: start_date + 9.days,
scope_count: 2,
scope_weight: 0,
completed_count: 0,
completed_weight: 0
}
])
end
context 'when the service returns an error' do
before do
stub_const('TimeboxReportService::EVENT_COUNT_LIMIT', 1)
end
it 'raises a GraphQL exception' do
expect { subject }.to raise_error(GraphQL::ExecutionError, 'Burnup chart could not be generated due to too many events')
end
it 'raises a GraphQL exception' do
expect { subject }.to raise_error(GraphQL::ExecutionError, 'Burnup chart could not be generated due to too many events')
end
end
end
......
......@@ -40,58 +40,39 @@ RSpec.describe 'Querying a Milestone' do
create(:resource_milestone_event, issue: issue, milestone: milestone, action: :add, created_at: '2020-01-05')
end
context 'when feature flag is enabled' do
context 'with insufficient license' do
before do
stub_feature_flags(burnup_charts: true)
stub_licensed_features(milestone_charts: false)
end
context 'with insufficient license' do
before do
stub_licensed_features(milestone_charts: false)
end
it 'returns an error' do
post_graphql(query, current_user: current_user)
expect(graphql_errors).to include(a_hash_including('message' => 'Milestone does not support burnup charts'))
end
end
context 'with correct license' do
before do
stub_licensed_features(milestone_charts: true, issue_weights: true)
end
it 'returns burnup chart data' do
post_graphql(query, current_user: current_user)
it 'returns an error' do
post_graphql(query, current_user: current_user)
expect(subject).to eq({
'report' => {
'burnupTimeSeries' => [
{
'date' => '2020-01-05',
'scopeCount' => 1,
'scopeWeight' => 0,
'completedCount' => 0,
'completedWeight' => 0
}
]
}
})
end
expect(graphql_errors).to include(a_hash_including('message' => 'Milestone does not support burnup charts'))
end
end
context 'when feature flag is disabled' do
context 'with correct license' do
before do
stub_feature_flags(burnup_charts: false)
stub_licensed_features(milestone_charts: true, issue_weights: true)
end
it 'returns empty results' do
it 'returns burnup chart data' do
post_graphql(query, current_user: current_user)
expect(subject).to eq({ 'report' => { 'burnupTimeSeries' => nil } })
expect(subject).to eq({
'report' => {
'burnupTimeSeries' => [
{
'date' => '2020-01-05',
'scopeCount' => 1,
'scopeWeight' => 0,
'completedCount' => 0,
'completedWeight' => 0
}
]
}
})
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