Commit ef834f6b authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '337198-remove-performance_roadmap-ff' into 'master'

Remove `performance_roadmap` feature flag and related legacy code from Roadmap app

See merge request gitlab-org/gitlab!71828
parents 3aebfb4a 7be12de8
......@@ -9,7 +9,6 @@ module Types
DEFAULT_COMPLEXITY = 1
attr_reader :deprecation, :doc_reference
attr_writer :max_page_size # Can be removed with :performance_roadmap feature flag: https://gitlab.com/gitlab-org/gitlab/-/issues/337198
def initialize(**kwargs, &block)
@calls_gitaly = !!kwargs.delete(:calls_gitaly)
......
......@@ -182,7 +182,7 @@ export default {
<current-day-indicator :preset-type="presetType" :timeframe-item="timeframeItem" />
</span>
</div>
<gl-intersection-observer v-if="glFeatures.performanceRoadmap" @appear="handleScrolledToEnd">
<gl-intersection-observer @appear="handleScrolledToEnd">
<div
v-if="epicsFetchForNextPageInProgress"
class="gl-text-center gl-py-3"
......
......@@ -3,15 +3,12 @@ import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { mapState, mapActions } from 'vuex';
import { __, s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
EXTEND_AS,
EPICS_LIMIT_DISMISSED_COOKIE_NAME,
EPICS_LIMIT_DISMISSED_COOKIE_TIMEOUT,
DATE_RANGES,
} from '../constants';
import eventHub from '../event_hub';
import EpicsListEmpty from './epics_list_empty.vue';
import RoadmapFilters from './roadmap_filters.vue';
import RoadmapShell from './roadmap_shell.vue';
......@@ -31,7 +28,6 @@ export default {
RoadmapFilters,
RoadmapShell,
},
mixins: [glFeatureFlagsMixin()],
props: {
timeframeRangeType: {
type: String,
......@@ -59,14 +55,11 @@ export default {
'epics',
'milestones',
'timeframe',
'extendedTimeframe',
'epicsFetchInProgress',
'epicsFetchForTimeframeInProgress',
'epicsFetchResultEmpty',
'epicsFetchFailure',
'isChildEpics',
'hasFiltersApplied',
'milestonesFetchFailure',
'filterParams',
]),
showFilteredSearchbar() {
......@@ -91,62 +84,7 @@ export default {
this.fetchMilestones();
},
methods: {
...mapActions([
'fetchEpics',
'fetchEpicsForTimeframe',
'extendTimeframe',
'refreshEpicDates',
'fetchMilestones',
'refreshMilestoneDates',
]),
/**
* Once timeline is expanded (either with prepend or append)
* We need performing following actions;
*
* 1. Reset start and end edges of the timeline for
* infinite scrolling to continue further.
* 2. Re-render timeline bars to account for
* updated timeframe.
* 3. In case of prepending timeframe,
* reset scroll-position (due to DOM prepend).
*/
processExtendedTimeline({ extendAs = EXTEND_AS.PREPEND, roadmapTimelineEl, itemsCount = 0 }) {
// Re-render timeline bars with updated timeline
eventHub.$emit('refreshTimeline', {
todayBarReady: extendAs === EXTEND_AS.PREPEND,
});
if (extendAs === EXTEND_AS.PREPEND) {
// When DOM is prepended with elements
// we compensate the scrolling for added elements' width
roadmapTimelineEl.parentElement.scrollBy(
roadmapTimelineEl.querySelector('.timeline-header-item').clientWidth * itemsCount,
0,
);
}
},
handleScrollToExtend({ el: roadmapTimelineEl, extendAs = EXTEND_AS.PREPEND }) {
this.extendTimeframe({ extendAs });
this.refreshEpicDates();
this.refreshMilestoneDates();
this.$nextTick(() => {
this.fetchEpicsForTimeframe({
timeframe: this.extendedTimeframe,
})
.then(() => {
this.$nextTick(() => {
// Re-render timeline bars with updated timeline
this.processExtendedTimeline({
itemsCount: this.extendedTimeframe ? this.extendedTimeframe.length : 0,
extendAs,
roadmapTimelineEl,
});
});
})
.catch(() => {});
});
},
...mapActions(['fetchEpics', 'fetchMilestones']),
dismissTooManyEpicsWarning() {
Cookies.set(EPICS_LIMIT_DISMISSED_COOKIE_NAME, 'true', {
expires: EPICS_LIMIT_DISMISSED_COOKIE_TIMEOUT,
......@@ -193,8 +131,6 @@ export default {
:timeframe="timeframe"
:current-group-id="currentGroupId"
:has-filters-applied="hasFiltersApplied"
@onScrollToStart="handleScrollToExtend"
@onScrollToEnd="handleScrollToExtend"
/>
</div>
</div>
......
<script>
import { mapState } from 'vuex';
import { isInViewport } from '~/lib/utils/common_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { EXTEND_AS } from '../constants';
import eventHub from '../event_hub';
import epicsListSection from './epics_list_section.vue';
......@@ -16,7 +13,6 @@ export default {
milestonesListSection,
roadmapTimelineSection,
},
mixins: [glFeatureFlagsMixin()],
props: {
presetType: {
type: String,
......@@ -43,56 +39,16 @@ export default {
required: true,
},
},
data() {
return {
timeframeStartOffset: 0,
};
},
computed: {
...mapState(['defaultInnerHeight']),
displayMilestones() {
return Boolean(this.milestones.length);
},
},
mounted() {
this.$nextTick(() => {
// We're guarding this as in tests, `roadmapTimeline`
// is not ready when this line is executed.
if (this.$refs.roadmapTimeline) {
this.timeframeStartOffset = this.$refs.roadmapTimeline.$el
.querySelector('.timeline-header-item')
.querySelector('.item-sublabel .sublabel-value:first-child')
.getBoundingClientRect().left;
}
});
},
methods: {
handleScroll() {
const { scrollTop, scrollLeft, clientHeight, scrollHeight } = this.$el;
if (!this.glFeatures.roadmapDaterangeFilter) {
const timelineEdgeStartEl = this.$refs.roadmapTimeline.$el
.querySelector('.timeline-header-item')
.querySelector('.item-sublabel .sublabel-value:first-child');
const timelineEdgeEndEl = this.$refs.roadmapTimeline.$el
.querySelector('.timeline-header-item:last-child')
.querySelector('.item-sublabel .sublabel-value:last-child');
// If timeline was scrolled to start
if (isInViewport(timelineEdgeStartEl, { left: this.timeframeStartOffset })) {
this.$emit('onScrollToStart', {
el: this.$refs.roadmapTimeline.$el,
extendAs: EXTEND_AS.PREPEND,
});
} else if (isInViewport(timelineEdgeEndEl)) {
// If timeline was scrolled to end
this.$emit('onScrollToEnd', {
el: this.$refs.roadmapTimeline.$el,
extendAs: EXTEND_AS.APPEND,
});
}
}
eventHub.$emit('epicsListScrolled', { scrollTop, scrollLeft, clientHeight, scrollHeight });
},
},
......
......@@ -81,4 +81,4 @@ export const EPICS_LIMIT_DISMISSED_COOKIE_NAME = 'epics_limit_warning_dismissed'
export const EPICS_LIMIT_DISMISSED_COOKIE_TIMEOUT = 365;
export const ROADMAP_PAGE_SIZE = gon.features?.performanceRoadmap ? 50 : gon.roadmap_epics_limit;
export const ROADMAP_PAGE_SIZE = 50;
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { EXTEND_AS, ROADMAP_PAGE_SIZE } from '../constants';
import { ROADMAP_PAGE_SIZE } from '../constants';
import epicChildEpics from '../queries/epicChildEpics.query.graphql';
import groupEpics from '../queries/groupEpics.query.graphql';
import groupMilestones from '../queries/groupMilestones.query.graphql';
import * as epicUtils from '../utils/epic_utils';
import * as roadmapItemUtils from '../utils/roadmap_item_utils';
import {
getEpicsTimeframeRange,
sortEpics,
extendTimeframeForPreset,
} from '../utils/roadmap_utils';
import { getEpicsTimeframeRange, sortEpics } from '../utils/roadmap_utils';
import * as types from './mutation_types';
......@@ -185,44 +181,6 @@ export const fetchEpics = ({ state, commit, dispatch }, { endCursor } = {}) => {
.catch(() => dispatch('receiveEpicsFailure'));
};
export const fetchEpicsForTimeframe = ({ state, commit, dispatch }, { timeframe }) => {
commit(types.REQUEST_EPICS_FOR_TIMEFRAME);
return fetchGroupEpics(state, { timeframe })
.then(({ rawEpics, pageInfo }) => {
dispatch('receiveEpicsSuccess', {
rawEpics,
pageInfo,
newEpic: true,
timeframeExtended: true,
});
})
.catch(() => dispatch('receiveEpicsFailure'));
};
/**
* Adds more EpicItemTimeline cells to the start or end of the roadmap.
*
* @param extendAs An EXTEND_AS enum value
*/
export const extendTimeframe = ({ commit, state }, { extendAs }) => {
const isExtendTypePrepend = extendAs === EXTEND_AS.PREPEND;
const { presetType, timeframe } = state;
const timeframeToExtend = extendTimeframeForPreset({
extendAs,
presetType,
initialDate: isExtendTypePrepend
? roadmapItemUtils.timeframeStartDate(presetType, timeframe)
: roadmapItemUtils.timeframeEndDate(presetType, timeframe),
});
if (isExtendTypePrepend) {
commit(types.PREPEND_TIMEFRAME, timeframeToExtend);
} else {
commit(types.APPEND_TIMEFRAME, timeframeToExtend);
}
};
export const initItemChildrenFlags = ({ commit }, data) =>
commit(types.INIT_EPIC_CHILDREN_FLAGS, data);
......@@ -256,34 +214,6 @@ export const toggleEpic = ({ state, dispatch }, { parentItem }) => {
}
};
/**
* For epics that have no start or end date, this function updates their start and end dates
* so that the epic bars get longer to appear infinitely scrolling.
*/
export const refreshEpicDates = ({ commit, state }) => {
const { presetType, timeframe } = state;
const epics = state.epics.map((epic) => {
// Update child epic dates too
if (epic.children?.edges?.length > 0) {
epic.children.edges.map((childEpic) =>
roadmapItemUtils.processRoadmapItemDates(
childEpic,
roadmapItemUtils.timeframeStartDate(presetType, timeframe),
roadmapItemUtils.timeframeEndDate(presetType, timeframe),
),
);
}
return roadmapItemUtils.processRoadmapItemDates(
epic,
roadmapItemUtils.timeframeStartDate(presetType, timeframe),
roadmapItemUtils.timeframeEndDate(presetType, timeframe),
);
});
commit(types.SET_EPICS, epics);
};
export const fetchGroupMilestones = (
{ fullPath, presetType, filterParams, timeframe },
defaultTimeframe,
......
......@@ -8,7 +8,6 @@ module Groups
before_action :check_epics_available!
before_action :persist_roadmap_layout, only: [:show]
before_action do
push_frontend_feature_flag(:performance_roadmap, @group, default_enabled: :yaml)
push_frontend_feature_flag(:roadmap_daterange_filter, @group, type: :development, default_enabled: :yaml)
end
......
# frozen_string_literal: true
module SetsMaxPageSize
extend ActiveSupport::Concern
DEPRECATED_MAX_PAGE_SIZE = 1000
# We no longer need 1000 page size after epics roadmap pagination feature is released,
# after :performance_roadmap flag rollout we can safely use default max page size(100)
# for epics, child epics and child issues without breaking current roadmaps.
#
# When removing :performance_roadmap flag delete this file and remove its method call and
# the fields using the resolver will keep using default max page size.
# Flag rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/337198
private
def set_temp_limit_for(actor)
max_page_size =
if Feature.enabled?(:performance_roadmap, actor, default_enabled: :yaml)
context.schema.default_max_page_size
else
DEPRECATED_MAX_PAGE_SIZE
end
field.max_page_size = max_page_size # rubocop: disable Graphql/Descriptions
end
end
......@@ -3,7 +3,6 @@
module Resolvers
class EpicIssuesResolver < BaseResolver
include CachingArrayResolver
include SetsMaxPageSize
type Types::EpicIssueType.connection_type, null: true
......@@ -22,8 +21,6 @@ module Resolvers
end
def query_for(id)
set_temp_limit_for(epic.group) # Can be removed with :performance_roadmap feature flag: https://gitlab.com/gitlab-org/gitlab/-/issues/337198
::Epic.related_issues(ids: id)
end
......
......@@ -5,7 +5,6 @@ module Resolvers
include TimeFrameArguments
include SearchArguments
include LooksAhead
include SetsMaxPageSize
argument :iid, GraphQL::Types::ID,
required: false,
......@@ -102,8 +101,6 @@ module Resolvers
end
def find_epics(args)
set_temp_limit_for(group) # Can be removed with :performance_roadmap feature flag: https://gitlab.com/gitlab-org/gitlab/-/issues/337198
apply_lookahead(EpicsFinder.new(context[:current_user], args).execute)
end
......
---
name: performance_roadmap
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65652
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337198
milestone: '14.2'
type: development
group: group::product planning
default_enabled: true
......@@ -29,7 +29,6 @@ RSpec.describe 'group epic roadmap', :js do
before do
stub_licensed_features(epics: true)
stub_feature_flags(unfiltered_epic_aggregates: false)
stub_feature_flags(performance_roadmap: false)
stub_feature_flags(roadmap_daterange_filter: false)
sign_in(user)
......
......@@ -59,7 +59,6 @@ const createComponent = ({
currentGroupId = mockGroupId,
presetType = PRESET_TYPES.MONTHS,
hasFiltersApplied = false,
performanceRoadmap = false,
} = {}) => {
return shallowMountExtended(EpicsListSection, {
localVue,
......@@ -75,11 +74,6 @@ const createComponent = ({
currentGroupId,
hasFiltersApplied,
},
provide: {
glFeatures: {
performanceRoadmap,
},
},
});
};
......@@ -87,7 +81,6 @@ describe('EpicsListSectionComponent', () => {
let wrapper;
beforeEach(() => {
gon.features = { performanceRoadmap: false };
wrapper = createComponent();
});
......@@ -249,6 +242,8 @@ describe('EpicsListSectionComponent', () => {
});
describe('template', () => {
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
it('renders component container element with class `epics-list-section`', () => {
expect(wrapper.classes('epics-list-section')).toBe(true);
});
......@@ -263,44 +258,35 @@ describe('EpicsListSectionComponent', () => {
expect(wrapper.find('.epics-list-item-empty').exists()).toBe(true);
});
it('renders bottom shadow element when `showBottomShadow` prop is true', () => {
wrapper.setData({
showBottomShadow: true,
});
expect(wrapper.find('.epic-scroll-bottom-shadow').exists()).toBe(true);
it('renders gl-intersection-observer component', () => {
expect(findIntersectionObserver().exists()).toBe(true);
});
describe('when `performanceRoadmap` feature flag is enabled', () => {
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
it('calls action `fetchEpics` when gl-intersection-observer appears in viewport', () => {
const fakeFetchEpics = jest.spyOn(wrapper.vm, 'fetchEpics').mockImplementation();
beforeEach(() => {
gon.features = { performanceRoadmap: true };
wrapper = createComponent({ performanceRoadmap: true });
});
findIntersectionObserver().vm.$emit('appear');
it('renders gl-intersection-observer component', () => {
expect(findIntersectionObserver().exists()).toBe(true);
expect(fakeFetchEpics).toHaveBeenCalledWith({
endCursor: mockPageInfo.endCursor,
});
});
it('calls action `fetchEpics` when gl-intersection-observer appears in viewport', () => {
const fakeFetchEpics = jest.spyOn(wrapper.vm, 'fetchEpics').mockImplementation();
findIntersectionObserver().vm.$emit('appear');
it('renders gl-loading icon when epicsFetchForNextPageInProgress is true', async () => {
wrapper.vm.$store.commit(REQUEST_EPICS_FOR_NEXT_PAGE);
expect(fakeFetchEpics).toHaveBeenCalledWith({
endCursor: mockPageInfo.endCursor,
});
});
await wrapper.vm.$nextTick();
it('renders gl-loading icon when epicsFetchForNextPageInProgress is true', async () => {
wrapper.vm.$store.commit(REQUEST_EPICS_FOR_NEXT_PAGE);
await wrapper.vm.$nextTick();
expect(wrapper.findByTestId('next-page-loading').text()).toContain('Loading epics');
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
expect(wrapper.findByTestId('next-page-loading').text()).toContain('Loading epics');
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
it('renders bottom shadow element when `showBottomShadow` prop is true', () => {
wrapper.setData({
showBottomShadow: true,
});
expect(wrapper.find('.epic-scroll-bottom-shadow').exists()).toBe(true);
});
});
......
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Cookies from 'js-cookie';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import EpicsListEmpty from 'ee/roadmap/components/epics_list_empty.vue';
import RoadmapApp from 'ee/roadmap/components/roadmap_app.vue';
import RoadmapFilters from 'ee/roadmap/components/roadmap_filters.vue';
import RoadmapShell from 'ee/roadmap/components/roadmap_shell.vue';
import { PRESET_TYPES, EXTEND_AS } from 'ee/roadmap/constants';
import eventHub from 'ee/roadmap/event_hub';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import createStore from 'ee/roadmap/store';
import * as types from 'ee/roadmap/store/mutation_types';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
......@@ -20,7 +18,6 @@ import {
mockSortedBy,
mockSvgPath,
mockTimeframeInitialDate,
rawEpics,
} from 'ee_jest/roadmap/mock_data';
describe('RoadmapApp', () => {
......@@ -155,71 +152,6 @@ describe('RoadmapApp', () => {
});
});
describe('extending the roadmap timeline', () => {
let roadmapTimelineEl;
beforeEach(() => {
store.dispatch('receiveEpicsSuccess', { rawEpics: rawEpics.slice(0, 2) });
wrapper = createComponent(mount);
roadmapTimelineEl = wrapper.find('.roadmap-timeline-section').element;
});
it('updates timeline by extending timeframe from the start when called with extendType as `prepend`', () => {
jest.spyOn(eventHub, '$emit').mockImplementation();
wrapper.vm.processExtendedTimeline({
extendType: EXTEND_AS.PREPEND,
roadmapTimelineEl,
itemsCount: 0,
});
expect(eventHub.$emit).toHaveBeenCalledWith('refreshTimeline', expect.any(Object));
expect(roadmapTimelineEl.parentElement.scrollBy).toHaveBeenCalled();
});
it('updates timeline by extending timeframe from the end when called with extendType as `append`', () => {
jest.spyOn(eventHub, '$emit').mockImplementation();
wrapper.vm.processExtendedTimeline({
extendType: EXTEND_AS.APPEND,
roadmapTimelineEl,
itemsCount: 0,
});
expect(eventHub.$emit).toHaveBeenCalledWith('refreshTimeline', expect.any(Object));
});
it('updates the store and refreshes roadmap with extended timeline based on provided extendType', () => {
jest.spyOn(wrapper.vm, 'extendTimeframe').mockImplementation();
jest.spyOn(wrapper.vm, 'refreshEpicDates').mockImplementation();
jest.spyOn(wrapper.vm, 'refreshMilestoneDates').mockImplementation();
jest.spyOn(wrapper.vm, 'fetchEpicsForTimeframe').mockResolvedValue();
const extendType = EXTEND_AS.PREPEND;
wrapper.vm.handleScrollToExtend({ el: roadmapTimelineEl, extendAs: extendType });
expect(wrapper.vm.extendTimeframe).toHaveBeenCalledWith({ extendAs: extendType });
expect(wrapper.vm.refreshEpicDates).toHaveBeenCalled();
expect(wrapper.vm.refreshMilestoneDates).toHaveBeenCalled();
});
it('calls `fetchEpicsForTimeframe` with extended timeframe array', async () => {
jest.spyOn(wrapper.vm, 'fetchEpicsForTimeframe').mockResolvedValue();
const extendType = EXTEND_AS.PREPEND;
wrapper.vm.handleScrollToExtend({ el: roadmapTimelineEl, extendAs: extendType });
await nextTick();
expect(wrapper.vm.fetchEpicsForTimeframe).toHaveBeenCalledWith(
expect.objectContaining({
timeframe: wrapper.vm.extendedTimeframe,
}),
);
});
});
describe('roadmap epics limit warning', () => {
beforeEach(() => {
wrapper = createComponent();
......
......@@ -79,12 +79,6 @@ describe('RoadmapShell', () => {
store = null;
});
describe('data', () => {
it('returns default data props', () => {
expect(wrapper.vm.timeframeStartOffset).toBe(0);
});
});
describe('methods', () => {
beforeEach(() => {
document.body.innerHTML +=
......
import MockAdapter from 'axios-mock-adapter';
import { PRESET_TYPES, EXTEND_AS } from 'ee/roadmap/constants';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import groupMilestones from 'ee/roadmap/queries/groupMilestones.query.graphql';
import * as actions from 'ee/roadmap/store/actions';
import * as types from 'ee/roadmap/store/mutation_types';
......@@ -14,9 +14,7 @@ import {
mockGroupId,
basePath,
mockTimeframeInitialDate,
mockTimeframeMonthsPrepend,
mockTimeframeMonthsAppend,
rawEpics,
mockRawEpic,
mockRawEpic2,
mockFormattedEpic,
......@@ -219,104 +217,6 @@ describe('Roadmap Vuex Actions', () => {
});
});
describe('fetchEpicsForTimeframe', () => {
describe('success', () => {
it('should perform REQUEST_EPICS_FOR_TIMEFRAME mutation and dispatch receiveEpicsSuccess action when request is successful', () => {
jest.spyOn(epicUtils.gqClient, 'query').mockReturnValue(
Promise.resolve({
data: mockGroupEpicsQueryResponse.data,
}),
);
return testAction(
actions.fetchEpicsForTimeframe,
{ timeframe: mockTimeframeMonths },
state,
[
{
type: types.REQUEST_EPICS_FOR_TIMEFRAME,
},
],
[
{
type: 'receiveEpicsSuccess',
payload: {
rawEpics: mockGroupEpics,
pageInfo: mockPageInfo,
newEpic: true,
timeframeExtended: true,
},
},
],
);
});
});
describe('failure', () => {
it('should perform REQUEST_EPICS_FOR_TIMEFRAME mutation and dispatch requestEpicsFailure action when request fails', () => {
jest.spyOn(epicUtils.gqClient, 'query').mockRejectedValue();
return testAction(
actions.fetchEpicsForTimeframe,
{ timeframe: mockTimeframeMonths },
state,
[
{
type: types.REQUEST_EPICS_FOR_TIMEFRAME,
},
],
[
{
type: 'receiveEpicsFailure',
},
],
);
});
});
});
describe('extendTimeframe', () => {
it('should prepend to timeframe when called with extend type prepend', () => {
return testAction(
actions.extendTimeframe,
{ extendAs: EXTEND_AS.PREPEND },
state,
[{ type: types.PREPEND_TIMEFRAME, payload: mockTimeframeMonthsPrepend }],
[],
);
});
it('should append to timeframe when called with extend type append', () => {
return testAction(
actions.extendTimeframe,
{ extendAs: EXTEND_AS.APPEND },
state,
[{ type: types.APPEND_TIMEFRAME, payload: mockTimeframeMonthsAppend }],
[],
);
});
});
describe('refreshEpicDates', () => {
it('should update epics after refreshing epic dates to match with updated timeframe', () => {
const epics = rawEpics.map((epic) =>
roadmapItemUtils.formatRoadmapItemDetails(
epic,
state.timeframeStartDate,
state.timeframeEndDate,
),
);
return testAction(
actions.refreshEpicDates,
{},
{ ...state, timeframe: mockTimeframeMonths.concat(mockTimeframeMonthsAppend), epics },
[{ type: types.SET_EPICS, payload: epics }],
[],
);
});
});
describe('requestChildrenEpics', () => {
const parentItemId = '41';
it('should set `itemChildrenFetchInProgress` in childrenFlags for parentItem to true', () => {
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting epic issues information' do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:project) { create(:project, group: group) }
before_all do
group.add_maintainer(user)
end
before do
stub_licensed_features(epics: true)
end
context 'when toggling performance_roadmap feature flag' do
let_it_be(:epic) { create(:epic, group: group, state: 'opened') }
let_it_be(:issue1) { create(:issue, project: project) }
let_it_be(:issue2) { create(:issue, project: project) }
let(:query) do
epic_issues_query(epic)
end
before_all do
create(:epic_issue, epic: epic, issue: issue1)
create(:epic_issue, epic: epic, issue: issue2)
end
def epic_issues_query(epic)
epic_issues_fragment = <<~EPIC_ISSUE
issues {
nodes {
id
}
}
EPIC_ISSUE
graphql_query_for(
:group,
{ 'fullPath' => epic.group.full_path },
query_graphql_field(
'epic', { iid: epic.iid },
epic_issues_fragment
)
)
end
def epic_issues_count
graphql_data_at(:group, :epic, :issues, :nodes).count
end
it 'returns epics with max page based on feature flag status' do
stub_const('SetsMaxPageSize::DEPRECATED_MAX_PAGE_SIZE', 1)
post_graphql(epic_issues_query(epic), current_user: user)
expect(epic_issues_count).to eq(2)
stub_feature_flags(performance_roadmap: false)
post_graphql(epic_issues_query(epic), current_user: user)
expect(epic_issues_count).to eq(1)
stub_feature_flags(performance_roadmap: true)
post_graphql(epic_issues_query(epic), current_user: user)
expect(epic_issues_count).to eq(2)
end
end
end
......@@ -13,30 +13,6 @@ RSpec.describe 'getting epics information' do
stub_licensed_features(epics: true)
end
context 'when performance_roadmap flag is disabled' do
let_it_be(:epic1) { create(:epic, group: group, state: 'opened') }
let_it_be(:epic2) { create(:epic, group: group, state: 'opened') }
def epics_count
graphql_data_at(:group, :epics, :nodes).count
end
it 'returns epics within timeframe' do
stub_const('SetsMaxPageSize::DEPRECATED_MAX_PAGE_SIZE', 1)
post_graphql(epics_query_by_hash(group), current_user: user)
expect(epics_count).to eq(2)
stub_feature_flags(performance_roadmap: false)
post_graphql(epics_query_by_hash(group), current_user: user)
expect(epics_count).to eq(1)
stub_feature_flags(performance_roadmap: true)
post_graphql(epics_query_by_hash(group), current_user: user)
expect(epics_count).to eq(2)
end
end
describe 'query for epics which start with an iid' do
let_it_be(:epic1) { create(:epic, group: group, iid: 11) }
let_it_be(:epic2) { create(:epic, group: group, iid: 22) }
......
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