Commit 86667196 authored by ap4y's avatar ap4y

Bootstrap pages and components for policy editor

This commit contains preliminary work for the threat monitoring policy
editor: new route and views, new frontend app, related navigation
changes. Frontend feature flag was also introduced as well as a link
to the new page behind the feature flag.
parent 870cc632
import initPolicyEditorApp from 'ee/threat_monitoring/policy_editor';
document.addEventListener('DOMContentLoaded', initPolicyEditorApp);
......@@ -58,6 +58,10 @@ export default {
type: String,
required: true,
},
newPolicyPath: {
type: String,
required: true,
},
},
data() {
return {
......@@ -193,7 +197,10 @@ export default {
/>
</gl-tab>
<gl-tab ref="networkPolicyTab" :title="s__('ThreatMonitoring|Policies')">
<network-policy-list :documentation-path="documentationPath" />
<network-policy-list
:documentation-path="documentationPath"
:new-policy-path="newPolicyPath"
/>
</gl-tab>
</gl-tabs>
</section>
......
......@@ -16,6 +16,8 @@ import { setUrlFragment } from '~/lib/utils/url_utility';
import EnvironmentPicker from './environment_picker.vue';
import NetworkPolicyEditor from './network_policy_editor.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
GlTable,
......@@ -29,11 +31,16 @@ export default {
EnvironmentPicker,
NetworkPolicyEditor,
},
mixins: [glFeatureFlagsMixin()],
props: {
documentationPath: {
type: String,
required: true,
},
newPolicyPath: {
type: String,
required: true,
},
},
data() {
return { selectedPolicyName: null, initialManifest: null, initialEnforcementStatus: null };
......@@ -149,8 +156,17 @@ export default {
</div>
<div class="pt-3 px-3 bg-gray-light">
<div class="row">
<div class="row justify-content-between align-items-center">
<environment-picker ref="environmentsPicker" />
<div v-if="glFeatures.networkPolicyEditor" class="col-sm-auto">
<gl-button
category="secondary"
variant="info"
:href="newPolicyPath"
data-testid="new-policy"
>{{ s__('NetworkPolicies|New policy') }}</gl-button
>
</div>
</div>
</div>
......
<script>
export default {
name: 'PolicyEditor',
};
</script>
<template>
<section>
<header class="my-3">
<h2 class="h3 mb-1">
{{ s__('NetworkPolicies|Policy description') }}
</h2>
</header>
</section>
</template>
......@@ -14,6 +14,7 @@ export default () => {
emptyStateSvgPath,
wafNoDataSvgPath,
networkPolicyNoDataSvgPath,
newPolicyPath,
documentationPath,
defaultEnvironmentId,
showUserCallout,
......@@ -46,6 +47,7 @@ export default () => {
showUserCallout: parseBoolean(showUserCallout),
userCalloutId,
userCalloutsPath,
newPolicyPath,
},
});
},
......
import Vue from 'vue';
import PolicyEditorApp from './components/policy_editor/app.vue';
import createStore from './store';
export default () => {
const el = document.querySelector('#js-policy-builder-app');
const { networkPoliciesEndpoint } = el.dataset;
const store = createStore();
store.dispatch('networkPolicies/setEndpoints', {
networkPoliciesEndpoint,
});
return new Vue({
el,
store,
render(createElement) {
return createElement(PolicyEditorApp, {});
},
});
};
......@@ -3,5 +3,15 @@
module Projects
class ThreatMonitoringController < Projects::ApplicationController
before_action :authorize_read_threat_monitoring!
before_action :verify_network_policy_editor_flag!, only: :new
before_action do
push_frontend_feature_flag(:network_policy_editor, project)
end
private
def verify_network_policy_editor_flag!
render_404 unless Feature.enabled?(:network_policy_editor, project, default_enabled: false)
end
end
end
......@@ -133,6 +133,7 @@ module EE
projects/dependencies#index
projects/licenses#index
projects/threat_monitoring#show
projects/threat_monitoring#new
]
end
......
......@@ -37,7 +37,7 @@
%span= _('License Compliance')
- if project_nav_tab?(:threat_monitoring)
= nav_link(path: 'projects/threat_monitoring#show') do
= nav_link(controller: ['projects/threat_monitoring']) do
= link_to project_threat_monitoring_path(@project), title: _('Threat Monitoring') do
%span= _('Threat Monitoring')
......
- add_to_breadcrumbs s_("ThreatMonitoring|Threat Monitoring"), project_threat_monitoring_path(@project)
- breadcrumb_title s_("NetworkPolicies|New policy")
- page_title s_("NetworkPolicies|Policy editor")
#js-policy-builder-app{ data: { network_policies_endpoint: project_security_network_policies_path(@project) } }
......@@ -12,6 +12,7 @@
network_policy_statistics_endpoint: summary_project_security_network_policies_path(@project, format: :json),
environments_endpoint: project_environments_path(@project),
network_policies_endpoint: project_security_network_policies_path(@project),
new_policy_path: new_project_threat_monitoring_policy_path(@project),
default_environment_id: default_environment_id,
user_callouts_path: user_callouts_path,
user_callout_id: UserCalloutsHelper::THREAT_MONITORING_INFO,
......
......@@ -37,7 +37,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :subscriptions, only: [:create, :destroy]
resource :threat_monitoring, only: [:show], controller: :threat_monitoring
resource :threat_monitoring, only: [:show], controller: :threat_monitoring do
resources :policies, only: [:new], controller: :threat_monitoring
end
resources :protected_environments, only: [:create, :update, :destroy], constraints: { id: /\d+/ } do
collection do
......
......@@ -6,9 +6,9 @@ RSpec.describe Projects::ThreatMonitoringController do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:user) { create(:user) }
describe 'GET show' do
subject { get :show, params: { namespace_id: project.namespace, project_id: project } }
describe 'GET show' do
context 'with authorized user' do
before do
project.add_developer(user)
......@@ -30,7 +30,87 @@ RSpec.describe Projects::ThreatMonitoringController do
context 'when feature is not available' do
before do
stub_feature_flags(threat_monitoring: false)
stub_licensed_features(threat_monitoring: false)
end
it 'returns 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'with unauthorized user' do
before do
sign_in(user)
end
context 'when feature is available' do
before do
stub_licensed_features(threat_monitoring: true)
end
it 'returns 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'with anonymous user' do
it 'returns 302' do
subject
expect(response).to have_gitlab_http_status(:found)
expect(response).to redirect_to(new_user_session_path)
end
end
end
describe 'GET new' do
subject { get :new, params: { namespace_id: project.namespace, project_id: project } }
context 'with authorized user' do
before do
project.add_developer(user)
sign_in(user)
end
context 'when feature is available' do
before do
stub_licensed_features(threat_monitoring: true)
end
context 'and feature flag is disabled' do
before do
stub_feature_flags(network_policy_editor: false)
end
it 'returns 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'and feature flag is enabled' do
before do
stub_feature_flags(network_policy_editor: true)
end
it 'renders the show template' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:new)
end
end
end
context 'when feature is not available' do
before do
stub_licensed_features(threat_monitoring: false)
end
......
......@@ -22,6 +22,7 @@ exports[`ThreatMonitoringApp component given there is a default environment with
>
<network-policy-list-stub
documentationpath="/docs"
newpolicypath="/policy/new"
/>
</gl-tab-stub>
`;
......
......@@ -6,10 +6,10 @@ exports[`NetworkPolicyList component renders policies table 1`] = `
<table
aria-busy="false"
aria-colcount="3"
aria-describedby="__BVID__45__caption_"
aria-describedby="__BVID__143__caption_"
aria-multiselectable="false"
class="table b-table gl-table table-hover b-table-stacked-md b-table-selectable b-table-select-single"
id="__BVID__45"
id="__BVID__143"
role="table"
>
<!---->
......
......@@ -9,6 +9,7 @@ import ThreatMonitoringFilters from 'ee/threat_monitoring/components/threat_moni
const defaultEnvironmentId = 3;
const documentationPath = '/docs';
const newPolicyPath = '/policy/new';
const chartEmptyStateSvgPath = '/chart-svgs';
const emptyStateSvgPath = '/svgs';
const wafNoDataSvgPath = '/waf-no-data-svg';
......@@ -42,6 +43,7 @@ describe('ThreatMonitoringApp component', () => {
wafNoDataSvgPath,
networkPolicyNoDataSvgPath,
documentationPath,
newPolicyPath,
showUserCallout: true,
userCalloutId,
userCalloutsPath,
......
......@@ -13,7 +13,7 @@ describe('NetworkPolicyList component', () => {
let store;
let wrapper;
const factory = ({ propsData, state, data } = {}) => {
const factory = ({ propsData, state, data, provide } = {}) => {
store = createStore();
Object.assign(store.state.networkPolicies, {
isLoadingPolicies: false,
......@@ -26,10 +26,12 @@ describe('NetworkPolicyList component', () => {
wrapper = mount(NetworkPolicyList, {
propsData: {
documentationPath: 'documentation_path',
newPolicyPath: 'new_policy_path',
...propsData,
},
data,
store,
provide,
});
};
......@@ -56,6 +58,28 @@ describe('NetworkPolicyList component', () => {
expect(findEnvironmentsPicker().exists()).toBe(true);
});
it('does not render the new policy button', () => {
const button = wrapper.find('[data-testid="new-policy"]');
expect(button.exists()).toBe(false);
});
describe('given the networkPolicyEditor feature flag is enabled', () => {
beforeEach(() => {
factory({
provide: {
glFeatures: {
networkPolicyEditor: true,
},
},
});
});
it('renders the new policy button', () => {
const button = wrapper.find('[data-testid="new-policy"]');
expect(button.exists()).toBe(true);
});
});
it('renders policies table', () => {
expect(findPoliciesTable().element).toMatchSnapshot();
});
......
import { shallowMount } from '@vue/test-utils';
import PolicyEditorApp from 'ee/threat_monitoring/components/policy_editor/app.vue';
import createStore from 'ee/threat_monitoring/store';
describe('PolicyEditorApp component', () => {
let store;
let wrapper;
const factory = ({ propsData, state, options } = {}) => {
store = createStore();
Object.assign(store.state.threatMonitoring, {
...state,
});
wrapper = shallowMount(PolicyEditorApp, {
propsData: {
...propsData,
},
store,
...options,
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders the header title', () => {
factory({});
expect(wrapper.find('header').exists()).toBe(true);
});
});
......@@ -154,6 +154,7 @@ RSpec.describe ProjectsHelper do
projects/dependencies#index
projects/licenses#index
projects/threat_monitoring#show
projects/threat_monitoring#new
]
end
......
......@@ -15537,6 +15537,9 @@ msgstr ""
msgid "NetworkPolicies|Name"
msgstr ""
msgid "NetworkPolicies|New policy"
msgstr ""
msgid "NetworkPolicies|No policies detected"
msgstr ""
......@@ -15549,6 +15552,12 @@ msgstr ""
msgid "NetworkPolicies|Policy definition"
msgstr ""
msgid "NetworkPolicies|Policy description"
msgstr ""
msgid "NetworkPolicies|Policy editor"
msgstr ""
msgid "NetworkPolicies|Something went wrong, failed to update policy"
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