Commit 91b24336 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska Committed by Etienne Baqué

Add Escalation policy Empty state [RUN ALL RSPEC] [RUN AS-IF-FOSS]

parent 92c4930b
---
name: escalation_policies_mvc
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60524
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329347
milestone: '13.12'
type: development
group: group::monitor
default_enabled: false
<script>
import { GlEmptyState, GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
export const i18n = {
emptyState: {
title: s__('EscalationPolicies|Create an escalation policy in GitLab'),
description: s__(
"EscalationPolicies|Set up escalation policies to define who is paged, and when, in the event the first users paged don't respond.",
),
button: s__('EscalationPolicies|Add an escalation policy'),
},
};
export default {
i18n,
components: {
GlEmptyState,
GlButton,
},
inject: ['emptyEscalationPoliciesSvgPath'],
methods: {
addEscalationPolicy() {
// TODO: Add method as part of https://gitlab.com/gitlab-org/gitlab/-/issues/268356
},
},
};
</script>
<template>
<gl-empty-state
:title="$options.i18n.emptyState.title"
:description="$options.i18n.emptyState.description"
:svg-path="emptyEscalationPoliciesSvgPath"
>
<template #actions>
<gl-button variant="info" @click="addEscalationPolicy">{{
$options.i18n.emptyState.button
}}</gl-button>
</template>
</gl-empty-state>
</template>
import Vue from 'vue';
import EscalationPoliciesWrapper from './components/escalation_policies_wrapper.vue';
export default () => {
const el = document.querySelector('.js-escalation-policies');
if (!el) return null;
const { emptyEscalationPoliciesSvgPath } = el.dataset;
return new Vue({
el,
provide: {
emptyEscalationPoliciesSvgPath,
},
render(createElement) {
return createElement(EscalationPoliciesWrapper);
},
});
};
import initOnCallScheduleManagement from 'ee/escalation_policies';
initOnCallScheduleManagement();
# frozen_string_literal: true
module Projects
module IncidentManagement
class EscalationPoliciesController < Projects::ApplicationController
before_action :authorize_read_incident_management_escalation_policy!
feature_category :incident_management
def index
end
end
end
end
# frozen_string_literal: true
module IncidentManagement
module EscalationPolicyHelper
def escalation_policy_data
{
'empty_escalation_policies_svg_path' => image_path('illustrations/empty-state/empty-escalation.svg')
}
end
end
end
...@@ -131,6 +131,7 @@ class License < ApplicationRecord ...@@ -131,6 +131,7 @@ class License < ApplicationRecord
ci_project_subscriptions ci_project_subscriptions
incident_timeline_view incident_timeline_view
oncall_schedules oncall_schedules
escalation_policies
export_user_permissions export_user_permissions
] ]
EEP_FEATURES.freeze EEP_FEATURES.freeze
......
...@@ -133,6 +133,11 @@ module EE ...@@ -133,6 +133,11 @@ module EE
::Gitlab::IncidentManagement.oncall_schedules_available?(@subject) ::Gitlab::IncidentManagement.oncall_schedules_available?(@subject)
end end
with_scope :subject
condition(:escalation_policies_available) do
::Gitlab::IncidentManagement.escalation_policies_available?(@subject)
end
rule { visual_review_bot }.policy do rule { visual_review_bot }.policy do
prevent :read_note prevent :read_note
enable :create_note enable :create_note
...@@ -163,6 +168,7 @@ module EE ...@@ -163,6 +168,7 @@ module EE
end end
rule { oncall_schedules_available & can?(:reporter_access) }.enable :read_incident_management_oncall_schedule rule { oncall_schedules_available & can?(:reporter_access) }.enable :read_incident_management_oncall_schedule
rule { escalation_policies_available & can?(:reporter_access) }.enable :read_incident_management_escalation_policy
rule { can?(:developer_access) }.policy do rule { can?(:developer_access) }.policy do
enable :admin_issue_board enable :admin_issue_board
...@@ -245,6 +251,7 @@ module EE ...@@ -245,6 +251,7 @@ module EE
rule { license_scanning_enabled & can?(:maintainer_access) }.enable :admin_software_license_policy rule { license_scanning_enabled & can?(:maintainer_access) }.enable :admin_software_license_policy
rule { oncall_schedules_available & can?(:maintainer_access) }.enable :admin_incident_management_oncall_schedule rule { oncall_schedules_available & can?(:maintainer_access) }.enable :admin_incident_management_oncall_schedule
rule { escalation_policies_available & can?(:maintainer_access) }.enable :admin_incident_management_escalation_policy
rule { auditor }.policy do rule { auditor }.policy do
enable :public_user_access enable :public_user_access
......
- page_title _('Escalation policies')
.js-escalation-policies{ data: escalation_policy_data }
- return unless project_nav_tab? :escalation_policy
= nav_link(controller: :escalation_policies) do
= link_to project_incident_management_escalation_policies_path(@project), title: _('Escalation policies') do
%span
= _('Escalation Policies')
...@@ -128,6 +128,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -128,6 +128,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
namespace :incident_management, path: '' do namespace :incident_management, path: '' do
resources :oncall_schedules, only: [:index], path: 'oncall_schedules' resources :oncall_schedules, only: [:index], path: 'oncall_schedules'
resources :escalation_policies, only: [:index], path: 'escalation_policies'
end end
resources :cluster_agents, only: [:show], param: :name resources :cluster_agents, only: [:show], param: :name
......
...@@ -12,6 +12,7 @@ module EE ...@@ -12,6 +12,7 @@ module EE
return false unless super return false unless super
insert_item_after(:incidents, on_call_schedules_menu_item) insert_item_after(:incidents, on_call_schedules_menu_item)
insert_item_after(:on_call_schedules, escalation_policies_menu_item)
true true
end end
...@@ -28,6 +29,17 @@ module EE ...@@ -28,6 +29,17 @@ module EE
item_id: :on_call_schedules item_id: :on_call_schedules
) )
end end
def escalation_policies_menu_item
return unless can?(context.current_user, :read_incident_management_escalation_policy, context.project)
::Sidebars::MenuItem.new(
title: _('Escalation policies'),
link: project_incident_management_escalation_policies_path(context.project),
active_routes: { controller: :escalation_policies },
item_id: :escalation_policies
)
end
end end
end end
end end
......
...@@ -5,5 +5,11 @@ module Gitlab ...@@ -5,5 +5,11 @@ module Gitlab
def self.oncall_schedules_available?(project) def self.oncall_schedules_available?(project)
project.feature_available?(:oncall_schedules) project.feature_available?(:oncall_schedules)
end end
def self.escalation_policies_available?(project)
oncall_schedules_available?(project) &&
project.feature_available?(:escalation_policies) &&
::Feature.enabled?(:escalation_policies_mvc, project, default_enabled: :yaml)
end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::IncidentManagement::EscalationPoliciesController do
let_it_be(:user_with_read_permissions) { create(:user) }
let_it_be(:user_with_admin_permissions) { create(:user) }
let_it_be(:project) { create(:project) }
let(:current_user) { user_with_admin_permissions }
describe 'GET #index' do
let(:request) do
get :index, params: { project_id: project.to_param, namespace_id: project.namespace.to_param }
end
before do
project.add_reporter(user_with_read_permissions)
project.add_maintainer(user_with_admin_permissions)
stub_licensed_features(oncall_schedules: true, escalation_policies: true)
sign_in(current_user)
end
context 'with read permissions' do
let(:current_user) { user_with_read_permissions }
it 'renders index with 200 status code' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:index)
end
end
context 'with admin permissions' do
let(:current_user) { user_with_admin_permissions }
it 'renders index with 200 status code' do
request
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:index)
end
end
context 'unauthorized' do
let(:current_user) { create(:user) }
it 'responds with 404' do
request
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'with feature flag off' do
before do
stub_feature_flags(escalation_policies_mvc: false)
end
it 'responds with 404' do
request
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'with unavailable feature' do
before do
stub_licensed_features(escalation_policies: false)
end
it 'responds with 404' do
request
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'with unavailable on-call schedules feature' do
before do
stub_licensed_features(oncall_schedules: false)
end
it 'responds with 404' do
request
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import OnCallScheduleWrapper, {
i18n,
} from 'ee/escalation_policies/components/escalation_policies_wrapper.vue';
describe('AlertManagementEmptyState', () => {
let wrapper;
const emptyEscalationPoliciesSvgPath = 'illustration/path.svg';
function mountComponent() {
wrapper = shallowMount(OnCallScheduleWrapper, {
provide: {
emptyEscalationPoliciesSvgPath,
},
});
}
beforeEach(() => {
mountComponent();
});
afterEach(() => {
wrapper.destroy();
});
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
describe('Empty state', () => {
it('shows empty state and passed correct attributes to it', () => {
expect(findEmptyState().exists()).toBe(true);
expect(findEmptyState().attributes()).toEqual({
title: i18n.emptyState.title,
description: i18n.emptyState.description,
svgpath: emptyEscalationPoliciesSvgPath,
});
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::EscalationPolicyHelper do
describe '#escalation_policy_data' do
subject(:data) { helper.escalation_policy_data }
it 'returns scalation policies data' do
is_expected.to eq(
'empty_escalation_policies_svg_path' => helper.image_path('illustrations/empty-state/empty-escalation.svg')
)
end
end
end
...@@ -22,4 +22,20 @@ RSpec.describe Sidebars::Projects::Menus::OperationsMenu do ...@@ -22,4 +22,20 @@ RSpec.describe Sidebars::Projects::Menus::OperationsMenu do
specify { is_expected.to be_nil } specify { is_expected.to be_nil }
end end
end end
describe 'Escalation policies' do
subject { described_class.new(context).items.index { |e| e.item_id == :escalation_policies } }
before do
stub_licensed_features(oncall_schedules: true, escalation_policies: true)
end
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
end end
...@@ -22,4 +22,39 @@ RSpec.describe Gitlab::IncidentManagement do ...@@ -22,4 +22,39 @@ RSpec.describe Gitlab::IncidentManagement do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
end end
describe '.escalation_policies_available?' do
subject { described_class.escalation_policies_available?(project) }
before do
stub_licensed_features(oncall_schedules: true, escalation_policies: true)
stub_feature_flags(escalation_policies_mvc: project)
end
it { is_expected.to be_truthy }
context 'when feature flag disabled' do
before do
stub_feature_flags(escalation_policies_mvc: false)
end
it { is_expected.to be_falsey }
end
context 'when escalation policies not avaialble' do
before do
stub_licensed_features(escalation_policies: false)
end
it { is_expected.to be_falsey }
end
context 'when on-call schedules not available' do
before do
stub_licensed_features(oncall_schedules: false)
end
it { is_expected.to be_falsey }
end
end
end end
...@@ -1483,6 +1483,76 @@ RSpec.describe ProjectPolicy do ...@@ -1483,6 +1483,76 @@ RSpec.describe ProjectPolicy do
end end
end end
describe 'Escalation Policies' do
using RSpec::Parameterized::TableSyntax
context ':read_incident_management_escalation_policy' do
let(:policy) { :read_incident_management_escalation_policy }
where(:role, :admin_mode, :allowed) do
:guest | nil | false
:reporter | nil | true
:developer | nil | true
:maintainer | nil | true
:owner | nil | true
:admin | false | false
:admin | true | true
end
before do
enable_admin_mode!(current_user) if admin_mode
allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(true)
end
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
context 'with unavailable escalation policies' do
before do
allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(false)
end
it { is_expected.to(be_disallowed(policy)) }
end
end
end
context ':admin_incident_management_escalation_policy' do
let(:policy) { :admin_incident_management_escalation_policy }
where(:role, :admin_mode, :allowed) do
:guest | nil | false
:reporter | nil | false
:developer | nil | false
:maintainer | nil | true
:owner | nil | true
:admin | false | false
:admin | true | true
end
before do
enable_admin_mode!(current_user) if admin_mode
allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(true)
end
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to(allowed ? be_allowed(policy) : be_disallowed(policy)) }
context 'with unavailable escalation policies' do
before do
allow(::Gitlab::IncidentManagement).to receive(:escalation_policies_available?).with(project).and_return(false)
end
it { is_expected.to(be_disallowed(policy)) }
end
end
end
end
context 'when project is readonly because the storage usage limit has been exceeded on the root namespace' do context 'when project is readonly because the storage usage limit has been exceeded on the root namespace' do
let(:current_user) { owner } let(:current_user) { owner }
let(:abilities) do let(:abilities) do
......
...@@ -284,6 +284,29 @@ RSpec.describe 'layouts/nav/sidebar/_project' do ...@@ -284,6 +284,29 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end end
end end
end end
describe 'Escalation policies' do
before do
allow(view).to receive(:current_user).and_return(user)
stub_licensed_features(oncall_schedules: true, escalation_policies: true)
end
it 'has a link to the escalation policies page' do
render
expect(rendered).to have_link('Escalation policies', href: project_incident_management_escalation_policies_path(project))
end
describe 'when the user does not have access' do
let(:user) { nil }
it 'does not have a link to the escalation policies page' do
render
expect(rendered).not_to have_link('Escalation policies')
end
end
end
end end
describe 'Settings > Operations' do describe 'Settings > Operations' do
......
...@@ -12971,6 +12971,21 @@ msgstr "" ...@@ -12971,6 +12971,21 @@ msgstr ""
msgid "Errors:" msgid "Errors:"
msgstr "" msgstr ""
msgid "Escalation Policies"
msgstr ""
msgid "Escalation policies"
msgstr ""
msgid "EscalationPolicies|Add an escalation policy"
msgstr ""
msgid "EscalationPolicies|Create an escalation policy in GitLab"
msgstr ""
msgid "EscalationPolicies|Set up escalation policies to define who is paged, and when, in the event the first users paged don't respond."
msgstr ""
msgid "Estimate" msgid "Estimate"
msgstr "" msgstr ""
......
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