Commit afcb3d3f authored by Etienne Baqué's avatar Etienne Baqué

Merge branch '268349-escalation-policies-empty-state' into 'master'

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

See merge request gitlab-org/gitlab!60524
parents e3ce6fbf 91b24336
---
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
ci_project_subscriptions
incident_timeline_view
oncall_schedules
escalation_policies
export_user_permissions
]
EEP_FEATURES.freeze
......
......@@ -133,6 +133,11 @@ module EE
::Gitlab::IncidentManagement.oncall_schedules_available?(@subject)
end
with_scope :subject
condition(:escalation_policies_available) do
::Gitlab::IncidentManagement.escalation_policies_available?(@subject)
end
rule { visual_review_bot }.policy do
prevent :read_note
enable :create_note
......@@ -163,6 +168,7 @@ module EE
end
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
enable :admin_issue_board
......@@ -245,6 +251,7 @@ module EE
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 { escalation_policies_available & can?(:maintainer_access) }.enable :admin_incident_management_escalation_policy
rule { auditor }.policy do
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
namespace :incident_management, path: '' do
resources :oncall_schedules, only: [:index], path: 'oncall_schedules'
resources :escalation_policies, only: [:index], path: 'escalation_policies'
end
resources :cluster_agents, only: [:show], param: :name
......
......@@ -12,6 +12,7 @@ module EE
return false unless super
insert_item_after(:incidents, on_call_schedules_menu_item)
insert_item_after(:on_call_schedules, escalation_policies_menu_item)
true
end
......@@ -28,6 +29,17 @@ module EE
item_id: :on_call_schedules
)
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
......
......@@ -5,5 +5,11 @@ module Gitlab
def self.oncall_schedules_available?(project)
project.feature_available?(:oncall_schedules)
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
# 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
specify { is_expected.to be_nil }
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
......@@ -22,4 +22,39 @@ RSpec.describe Gitlab::IncidentManagement do
it { is_expected.to be_falsey }
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
......@@ -1483,6 +1483,76 @@ RSpec.describe ProjectPolicy do
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
let(:current_user) { owner }
let(:abilities) do
......
......@@ -284,6 +284,29 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
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
describe 'Settings > Operations' do
......
......@@ -12971,6 +12971,21 @@ msgstr ""
msgid "Errors:"
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"
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