Commit c25257f4 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '222763-show-iteration-report-in-projects' into 'master'

Show iteration report in projects

See merge request gitlab-org/gitlab!37979
parents b1a94a5f d5261ef4
...@@ -35,11 +35,11 @@ export default { ...@@ -35,11 +35,11 @@ export default {
IterationReportTabs, IterationReportTabs,
}, },
apollo: { apollo: {
group: { namespace: {
query, query,
variables() { variables() {
return { return {
groupPath: this.groupPath, groupPath: this.fullPath,
iid: this.iterationIid, iid: this.iterationIid,
}; };
}, },
...@@ -56,7 +56,7 @@ export default { ...@@ -56,7 +56,7 @@ export default {
}, },
}, },
props: { props: {
groupPath: { fullPath: {
type: String, type: String,
required: true, required: true,
}, },
...@@ -74,17 +74,17 @@ export default { ...@@ -74,17 +74,17 @@ export default {
return { return {
isEditing: false, isEditing: false,
error: '', error: '',
group: { namespace: {
iteration: {}, iteration: {},
}, },
}; };
}, },
computed: { computed: {
iteration() { iteration() {
return this.group.iteration; return this.namespace.iteration;
}, },
hasIteration() { hasIteration() {
return !this.$apollo.queries.group.loading && this.iteration?.title; return !this.$apollo.queries.namespace.loading && this.iteration?.title;
}, },
status() { status() {
switch (this.iteration.state) { switch (this.iteration.state) {
...@@ -115,7 +115,7 @@ export default { ...@@ -115,7 +115,7 @@ export default {
<gl-alert v-if="error" variant="danger" @dismiss="error = ''"> <gl-alert v-if="error" variant="danger" @dismiss="error = ''">
{{ error }} {{ error }}
</gl-alert> </gl-alert>
<gl-loading-icon v-if="$apollo.queries.group.loading" class="gl-py-5" size="lg" /> <gl-loading-icon v-if="$apollo.queries.namespace.loading" class="gl-py-5" size="lg" />
<gl-empty-state <gl-empty-state
v-else-if="!hasIteration" v-else-if="!hasIteration"
:title="__('Could not find iteration')" :title="__('Could not find iteration')"
...@@ -123,7 +123,7 @@ export default { ...@@ -123,7 +123,7 @@ export default {
/> />
<iteration-form <iteration-form
v-else-if="isEditing" v-else-if="isEditing"
:group-path="groupPath" :group-path="fullPath"
:is-editing="true" :is-editing="true"
:iteration="iteration" :iteration="iteration"
@updated="isEditing = false" @updated="isEditing = false"
...@@ -158,8 +158,8 @@ export default { ...@@ -158,8 +158,8 @@ export default {
</div> </div>
<h3 ref="title" class="page-title">{{ iteration.title }}</h3> <h3 ref="title" class="page-title">{{ iteration.title }}</h3>
<div ref="description" v-text="iteration.description"></div> <div ref="description" v-text="iteration.description"></div>
<iteration-report-summary :group-path="groupPath" :iteration-id="iteration.id" /> <iteration-report-summary :group-path="fullPath" :iteration-id="iteration.id" />
<iteration-report-tabs :group-path="groupPath" :iteration-id="iteration.id" /> <iteration-report-tabs :group-path="fullPath" :iteration-id="iteration.id" />
</template> </template>
</div> </div>
</template> </template>
...@@ -51,7 +51,7 @@ export function initIterationForm() { ...@@ -51,7 +51,7 @@ export function initIterationForm() {
export function initIterationReport() { export function initIterationReport() {
const el = document.querySelector('.js-iteration'); const el = document.querySelector('.js-iteration');
const { groupPath, iterationIid, editIterationPath } = el.dataset; const { fullPath, iterationIid, editIterationPath } = el.dataset;
const canEdit = parseBoolean(el.dataset.canEdit); const canEdit = parseBoolean(el.dataset.canEdit);
return new Vue({ return new Vue({
...@@ -60,7 +60,7 @@ export function initIterationReport() { ...@@ -60,7 +60,7 @@ export function initIterationReport() {
render(createElement) { render(createElement) {
return createElement(IterationReport, { return createElement(IterationReport, {
props: { props: {
groupPath, fullPath,
iterationIid, iterationIid,
canEdit, canEdit,
editIterationPath, editIterationPath,
......
import { initIterationReport } from 'ee/iterations';
document.addEventListener('DOMContentLoaded', initIterationReport);
...@@ -14,14 +14,14 @@ class Groups::IterationsController < Groups::ApplicationController ...@@ -14,14 +14,14 @@ class Groups::IterationsController < Groups::ApplicationController
private private
def check_iterations_available! def check_iterations_available!
return render_404 unless group.feature_available?(:iterations) render_404 unless group.feature_available?(:iterations)
end end
def authorize_create_iteration! def authorize_create_iteration!
return render_404 unless can?(current_user, :create_iteration, group) render_404 unless can?(current_user, :create_iteration, group)
end end
def authorize_show_iteration! def authorize_show_iteration!
return render_404 unless can?(current_user, :read_iteration, group) render_404 unless can?(current_user, :read_iteration, group)
end end
end end
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
class Projects::IterationsController < Projects::ApplicationController class Projects::IterationsController < Projects::ApplicationController
before_action :check_iterations_available! before_action :check_iterations_available!
before_action :authorize_show_iteration!, only: [:index] before_action :authorize_show_iteration!
def index; end def index; end
def show; end
private private
def check_iterations_available! def check_iterations_available!
......
...@@ -3,4 +3,4 @@ ...@@ -3,4 +3,4 @@
- page_title _("Iterations") - page_title _("Iterations")
- if Feature.enabled?(:group_iterations, @group, default_enabled: true) - if Feature.enabled?(:group_iterations, @group, default_enabled: true)
.js-iteration{ data: { group_path: @group.full_path, iteration_iid: params[:id], can_edit: can?(current_user, :admin_iteration, @group).to_s, preview_markdown_path: preview_markdown_path(@group) } } .js-iteration{ data: { full_path: @group.full_path, iteration_iid: params[:id], can_edit: can?(current_user, :admin_iteration, @group).to_s, preview_markdown_path: preview_markdown_path(@group) } }
- add_to_breadcrumbs _("Iterations"), project_iterations_path(@project)
- breadcrumb_title params[:id]
- page_title _("Iterations")
- if Feature.enabled?(:project_iterations, @project.group)
.js-iteration{ data: { full_path: @project.group.full_path,
can_edit: can?(current_user, :admin_iteration, @project).to_s,
iteration_iid: params[:id],
preview_markdown_path: preview_markdown_path(@project) } }
...@@ -107,7 +107,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -107,7 +107,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end end
end end
resources :iterations, only: [:index] resources :iterations, only: [:index, :show], constraints: { id: /\d+/ }
end end
# End of the /-/ scope. # End of the /-/ scope.
......
...@@ -40,4 +40,18 @@ RSpec.describe 'User views iteration' do ...@@ -40,4 +40,18 @@ RSpec.describe 'User views iteration' do
end end
end end
end end
context 'without license' do
before do
stub_licensed_features(iterations: false)
sign_in(user)
end
it 'shows page not found' do
visit group_iteration_path(iteration.group, iteration)
expect(page).to have_title('Not Found')
expect(page).to have_content('Page Not Found')
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'User views iteration' do
let_it_be(:now) { Time.now }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:user) { create(:group_member, :maintainer, user: create(:user), group: group).user }
let_it_be(:iteration) { create(:iteration, :skip_future_date_validation, iid: 1, id: 2, group: group, title: 'Correct Iteration', start_date: now - 1.day, due_date: now) }
let_it_be(:other_iteration) { create(:iteration, :skip_future_date_validation, iid: 2, id: 1, group: group, title: 'Wrong Iteration', start_date: now - 4.days, due_date: now - 3.days) }
let_it_be(:issue) { create(:issue, project: project, iteration: iteration) }
let_it_be(:assigned_issue) { create(:issue, project: project, iteration: iteration, assignees: [user]) }
let_it_be(:closed_issue) { create(:closed_issue, project: project, iteration: iteration) }
let_it_be(:other_issue) { create(:issue, project: project, iteration: other_iteration) }
context 'with license' do
before do
stub_licensed_features(iterations: true)
sign_in(user)
end
context 'view an iteration', :js do
before do
visit project_iteration_path(project, iteration)
end
it 'shows iteration info and dates' do
expect(page).to have_content(iteration.title)
expect(page).to have_content(iteration.description)
expect(page).to have_content(iteration.start_date.strftime('%b %-d, %Y'))
expect(page).to have_content(iteration.due_date.strftime('%b %-d, %Y'))
end
it 'shows correct issues for issue' do
expect(page).to have_content(issue.title)
expect(page).to have_content(assigned_issue.title)
expect(page).to have_content(closed_issue.title)
expect(page).not_to have_content(other_issue.title)
end
end
end
context 'without license' do
before do
stub_licensed_features(iterations: false)
sign_in(user)
end
it 'shows page not found' do
visit project_iteration_path(project, iteration)
expect(page).to have_title('Not Found')
expect(page).to have_content('Page Not Found')
end
end
end
...@@ -5,7 +5,7 @@ import { GlEmptyState, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui'; ...@@ -5,7 +5,7 @@ import { GlEmptyState, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui';
describe('Iterations tabs', () => { describe('Iterations tabs', () => {
let wrapper; let wrapper;
const defaultProps = { const defaultProps = {
groupPath: 'gitlab-org', fullPath: 'gitlab-org',
iterationIid: '3', iterationIid: '3',
}; };
...@@ -18,7 +18,7 @@ describe('Iterations tabs', () => { ...@@ -18,7 +18,7 @@ describe('Iterations tabs', () => {
propsData: props, propsData: props,
mocks: { mocks: {
$apollo: { $apollo: {
queries: { group: { loading } }, queries: { namespace: { loading } },
}, },
}, },
stubs: { stubs: {
...@@ -70,7 +70,7 @@ describe('Iterations tabs', () => { ...@@ -70,7 +70,7 @@ describe('Iterations tabs', () => {
}); });
wrapper.setData({ wrapper.setData({
group: { namespace: {
iteration, iteration,
}, },
}); });
...@@ -94,7 +94,7 @@ describe('Iterations tabs', () => { ...@@ -94,7 +94,7 @@ describe('Iterations tabs', () => {
it('escapes html in description', async () => { it('escapes html in description', async () => {
wrapper.setData({ wrapper.setData({
group: { namespace: {
iteration: { iteration: {
...iteration, ...iteration,
description: `<img src=x onerror=alert(document.domain)>`, description: `<img src=x onerror=alert(document.domain)>`,
......
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