diff --git a/ee/app/assets/javascripts/iterations/components/iterations.vue b/ee/app/assets/javascripts/iterations/components/iterations.vue index 892b8fbb451c5a1e2449c062b09c441d27fbf881..a98d22e607fab1d66a321617e0a0c0d3537e30b3 100644 --- a/ee/app/assets/javascripts/iterations/components/iterations.vue +++ b/ee/app/assets/javascripts/iterations/components/iterations.vue @@ -17,7 +17,7 @@ export default { GlTabs, }, props: { - groupPath: { + fullPath: { type: String, required: true, }, @@ -33,7 +33,7 @@ export default { }, }, apollo: { - group: { + namespace: { query: GroupIterationQuery, variables() { return this.queryVariables; @@ -51,7 +51,7 @@ export default { }, data() { return { - group: { + namespace: { iterations: [], pageInfo: { hasNextPage: true, @@ -68,7 +68,7 @@ export default { computed: { queryVariables() { const vars = { - fullPath: this.groupPath, + fullPath: this.fullPath, state: this.state, }; @@ -83,10 +83,10 @@ export default { return vars; }, iterations() { - return this.group.iterations; + return this.namespace.iterations; }, loading() { - return this.$apollo.queries.group.loading; + return this.$apollo.queries.namespace.loading; }, state() { switch (this.tabIndex) { @@ -100,15 +100,15 @@ export default { } }, prevPage() { - return Number(this.group.pageInfo.hasPreviousPage); + return Number(this.namespace.pageInfo.hasPreviousPage); }, nextPage() { - return Number(this.group.pageInfo.hasNextPage); + return Number(this.namespace.pageInfo.hasNextPage); }, }, methods: { handlePageChange(page) { - const { startCursor, endCursor } = this.group.pageInfo; + const { startCursor, endCursor } = this.namespace.pageInfo; if (page > this.pagination.currentPage) { this.pagination = { diff --git a/ee/app/assets/javascripts/iterations/index.js b/ee/app/assets/javascripts/iterations/index.js index a6211f082c1be664a141db63e641ffeb9e2d3f05..b0bb365a1da668cbcb8eb215f50e31485d9219f7 100644 --- a/ee/app/assets/javascripts/iterations/index.js +++ b/ee/app/assets/javascripts/iterations/index.js @@ -21,7 +21,7 @@ export function initIterationsList() { render(createElement) { return createElement(Iterations, { props: { - groupPath: el.dataset.groupFullPath, + fullPath: el.dataset.fullPath, canAdmin: parseBoolean(el.dataset.canAdmin), newIterationPath: el.dataset.newIterationPath, }, diff --git a/ee/app/assets/javascripts/pages/projects/iterations/index.js b/ee/app/assets/javascripts/pages/projects/iterations/index.js new file mode 100644 index 0000000000000000000000000000000000000000..07f607e37fed125b58b90990c97e496d84633a97 --- /dev/null +++ b/ee/app/assets/javascripts/pages/projects/iterations/index.js @@ -0,0 +1,3 @@ +import { initIterationsList } from 'ee/iterations'; + +document.addEventListener('DOMContentLoaded', initIterationsList); diff --git a/ee/app/views/groups/iterations/index.html.haml b/ee/app/views/groups/iterations/index.html.haml index 87b395b030372ff11555335497713bc610f00665..c1fb6195c7a119f3c94ab6da310d8b57e4da98f1 100644 --- a/ee/app/views/groups/iterations/index.html.haml +++ b/ee/app/views/groups/iterations/index.html.haml @@ -1,4 +1,4 @@ - page_title _("Iterations") - if Feature.enabled?(:group_iterations, @group, default_enabled: true) - .js-iterations-list{ data: { group_full_path: @group.full_path, can_admin: can?(current_user, :create_iteration, @group).to_s, new_iteration_path: new_group_iteration_path(@group) } } + .js-iterations-list{ data: { full_path: @group.full_path, can_admin: can?(current_user, :create_iteration, @group).to_s, new_iteration_path: new_group_iteration_path(@group) } } diff --git a/ee/app/views/layouts/nav/sidebar/_project_iterations_link.html.haml b/ee/app/views/layouts/nav/sidebar/_project_iterations_link.html.haml index 34396a056c9f385f2b8952d525c97ac870c7a059..f388bd1d77606f6c6fe8fa093d3c2dc31ac184b2 100644 --- a/ee/app/views/layouts/nav/sidebar/_project_iterations_link.html.haml +++ b/ee/app/views/layouts/nav/sidebar/_project_iterations_link.html.haml @@ -1,4 +1,4 @@ -- return unless Feature.enabled?(:project_iterations, @project.group, default_enabled: false) +- return unless Feature.enabled?(:project_iterations, @project.group) - return unless @project.feature_available?(:iterations) - return unless can?(current_user, :read_iteration, @project) diff --git a/ee/app/views/projects/iterations/index.html.haml b/ee/app/views/projects/iterations/index.html.haml index e706be9ac9039513af26efee3bd2ba9cb07a988f..e8a5a6ced555187978446a56b2abc7cfbbb45337 100644 --- a/ee/app/views/projects/iterations/index.html.haml +++ b/ee/app/views/projects/iterations/index.html.haml @@ -1 +1,4 @@ - page_title _("Iterations") + +- if Feature.enabled?(:project_iterations, @project.group) + .js-iterations-list{ data: { full_path: @project.group.full_path } } diff --git a/ee/spec/features/projects/iterations/iterations_list_spec.rb b/ee/spec/features/projects/iterations/iterations_list_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..48ac66a23e36ccb0b40536c3f4ad8ec7a892f9ab --- /dev/null +++ b/ee/spec/features/projects/iterations/iterations_list_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Iterations list', :js do + let(:now) { Time.now } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :public, group: group) } + let_it_be(:user) { create(:user) } + let!(:started_iteration) { create(:iteration, :skip_future_date_validation, group: group, start_date: now - 1.day, due_date: now) } + let!(:upcoming_iteration) { create(:iteration, group: group, start_date: now + 1.day, due_date: now + 2.days) } + let!(:closed_iteration) { create(:closed_iteration, :skip_future_date_validation, group: group, start_date: now - 3.days, due_date: now - 2.days) } + + context 'as guest' do + before do + visit project_iterations_path(project) + end + + it 'shows iterations on each tab' do + expect(page).to have_link(started_iteration.title) + expect(page).to have_link(upcoming_iteration.title) + expect(page).not_to have_link(closed_iteration.title) + + click_link('Closed') + + expect(page).to have_link(closed_iteration.title) + expect(page).not_to have_link(started_iteration.title) + expect(page).not_to have_link(upcoming_iteration.title) + + click_link('All') + + expect(page).to have_link(started_iteration.title) + expect(page).to have_link(upcoming_iteration.title) + expect(page).to have_link(closed_iteration.title) + end + end + + context 'as authorized user' do + before do + project.add_developer(user) + sign_in(user) + visit project_iterations_path(project) + end + + it 'does not show "New iteration" button' do + expect(page).not_to have_link('New iteration') + end + end +end diff --git a/ee/spec/frontend/iterations/components/iterations_spec.js b/ee/spec/frontend/iterations/components/iterations_spec.js index dda0f0a766037acd4222b433829e944d24bec6ff..889e8dabe2a3aaf77fca12672f74d94bda1db184 100644 --- a/ee/spec/frontend/iterations/components/iterations_spec.js +++ b/ee/spec/frontend/iterations/components/iterations_spec.js @@ -6,7 +6,7 @@ import { GlAlert, GlLoadingIcon, GlPagination, GlTab, GlTabs } from '@gitlab/ui' describe('Iterations tabs', () => { let wrapper; const defaultProps = { - groupPath: 'gitlab-org', + fullPath: 'gitlab-org', }; const mountComponent = ({ props = defaultProps, loading = false } = {}) => { @@ -14,7 +14,7 @@ describe('Iterations tabs', () => { propsData: props, mocks: { $apollo: { - queries: { group: { loading } }, + queries: { namespace: { loading } }, }, }, stubs: { @@ -74,7 +74,7 @@ describe('Iterations tabs', () => { loading: false, }); wrapper.setData({ - group: { + namespace: { pageInfo: { hasNextPage: true, hasPreviousPage: false, @@ -102,7 +102,7 @@ describe('Iterations tabs', () => { expect(wrapper.vm.queryVariables).toEqual({ beforeCursor: 'first-item', lastPageSize: 20, - fullPath: defaultProps.groupPath, + fullPath: defaultProps.fullPath, state: 'opened', }); }); @@ -113,7 +113,7 @@ describe('Iterations tabs', () => { expect(wrapper.vm.queryVariables).toEqual({ afterCursor: 'last-item', firstPageSize: 20, - fullPath: defaultProps.groupPath, + fullPath: defaultProps.fullPath, state: 'opened', }); });