Commit 186b06fc authored by Alexander Turinske's avatar Alexander Turinske Committed by Terri Chu

Add `cluster_vulnerabilities` feature flag

parent f77caca4
...@@ -128,6 +128,7 @@ export default { ...@@ -128,6 +128,7 @@ export default {
</p> </p>
<gl-tabs> <gl-tabs>
<slot name="ee-security-tab"></slot>
<gl-tab> <gl-tab>
<template #title> <template #title>
<span data-testid="cluster-agent-token-count"> <span data-testid="cluster-agent-token-count">
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import AgentShowPage from './components/show.vue'; import AgentShowPage from 'ee_else_ce/clusters/agents/components/show.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
......
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
class Projects::ClusterAgentsController < Projects::ApplicationController class Projects::ClusterAgentsController < Projects::ApplicationController
before_action :authorize_can_read_cluster_agent! before_action :authorize_can_read_cluster_agent!
before_action do
push_frontend_feature_flag(:cluster_vulnerabilities, project, default_enabled: :yaml)
end
feature_category :kubernetes_management feature_category :kubernetes_management
def show def show
...@@ -17,3 +21,5 @@ class Projects::ClusterAgentsController < Projects::ApplicationController ...@@ -17,3 +21,5 @@ class Projects::ClusterAgentsController < Projects::ApplicationController
access_denied! access_denied!
end end
end end
Projects::ClusterAgentsController.prepend_mod_with('Projects::ClusterAgentsController')
---
name: cluster_vulnerabilities
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73321
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343917
milestone: '14.5'
type: development
group: group::container security
default_enabled: false
<script>
import { GlTab } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import AgentShowPage from '~/clusters/agents/components/show.vue';
export default {
i18n: {
securityTabTitle: s__('ClusterAgents|Security'),
},
components: { AgentShowPage, GlTab },
mixins: [glFeatureFlagMixin()],
props: {
agentName: {
required: true,
type: String,
},
projectPath: {
required: true,
type: String,
},
},
computed: {
showSecurityTab() {
return (
this.glFeatures.kubernetesClusterVulnerabilities && this.glFeatures.clusterVulnerabilities
);
},
},
};
</script>
<template>
<agent-show-page v-bind="$props">
<template v-if="showSecurityTab" #ee-security-tab>
<!-- Placeholder for https://gitlab.com/gitlab-org/gitlab/-/issues/343912-->
<gl-tab :title="$options.i18n.securityTabTitle"><div></div></gl-tab>
</template>
</agent-show-page>
</template>
# frozen_string_literal: true
module EE
module Projects
module ClusterAgentsController
extend ActiveSupport::Concern
prepended do
before_action do
push_licensed_feature(:kubernetes_cluster_vulnerabilities, project)
end
end
end
end
end
...@@ -174,6 +174,7 @@ class License < ApplicationRecord ...@@ -174,6 +174,7 @@ class License < ApplicationRecord
issuable_health_status issuable_health_status
jira_vulnerabilities_integration jira_vulnerabilities_integration
jira_issue_association_enforcement jira_issue_association_enforcement
kubernetes_cluster_vulnerabilities
license_scanning license_scanning
personal_access_token_expiration_policy personal_access_token_expiration_policy
prometheus_alerts prometheus_alerts
......
import { GlTab } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ClusterAgentShow from 'ee/clusters/agents/components/show.vue';
import CEClusterAgentShowPage from '~/clusters/agents/components/show.vue';
describe('ClusterAgentShow', () => {
let wrapper;
const agentName = 'best-agent';
const projectPath = 'path/to/project';
const createWrapper = ({ glFeatures = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(ClusterAgentShow, {
propsData: { agentName, projectPath },
provide: { glFeatures },
}),
);
};
const findTab = () => wrapper.findComponent(GlTab);
const findCEClusterAgentShowPage = () => wrapper.findComponent(CEClusterAgentShowPage);
afterEach(() => {
wrapper.destroy();
});
describe('tab behavior', () => {
it.each`
title | glFeatures | tabStatus
${'does not display the tab when no glFeatures are available'} | ${{}} | ${false}
${'does not display the tab when only the "clusterVulnerabilities" flag is true'} | ${{ clusterVulnerabilities: true }} | ${false}
${'does not display the tab when only the "kubernetesClusterVulnerabilities" flag is true'} | ${{ kubernetesClusterVulnerabilities: true }} | ${false}
${'does display the tab when both the "kubernetesClusterVulnerabilities" flag and "clusterVulnerabilities" flag are true'} | ${{ clusterVulnerabilities: true, kubernetesClusterVulnerabilities: true }} | ${true}
`('$title', async ({ glFeatures, tabStatus }) => {
createWrapper({ glFeatures });
await nextTick();
expect(findCEClusterAgentShowPage().props()).toStrictEqual({ agentName, projectPath });
expect(findTab().exists()).toBe(tabStatus);
});
});
});
...@@ -7403,6 +7403,9 @@ msgstr "" ...@@ -7403,6 +7403,9 @@ msgstr ""
msgid "ClusterAgents|Registration token" msgid "ClusterAgents|Registration token"
msgstr "" msgstr ""
msgid "ClusterAgents|Security"
msgstr ""
msgid "ClusterAgents|Select an Agent" msgid "ClusterAgents|Select an Agent"
msgstr "" msgstr ""
......
import { GlAlert, GlKeysetPagination, GlLoadingIcon, GlSprintf, GlTab } from '@gitlab/ui'; import { GlAlert, GlKeysetPagination, GlLoadingIcon, GlSprintf, GlTab } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ClusterAgentShow from '~/clusters/agents/components/show.vue'; import ClusterAgentShow from '~/clusters/agents/components/show.vue';
import TokenTable from '~/clusters/agents/components/token_table.vue'; import TokenTable from '~/clusters/agents/components/token_table.vue';
import getAgentQuery from '~/clusters/agents/graphql/queries/get_cluster_agent.query.graphql'; import getAgentQuery from '~/clusters/agents/graphql/queries/get_cluster_agent.query.graphql';
...@@ -40,28 +42,34 @@ describe('ClusterAgentShow', () => { ...@@ -40,28 +42,34 @@ describe('ClusterAgentShow', () => {
queryResponse || jest.fn().mockResolvedValue({ data: { project: { clusterAgent } } }); queryResponse || jest.fn().mockResolvedValue({ data: { project: { clusterAgent } } });
const apolloProvider = createMockApollo([[getAgentQuery, agentQueryResponse]]); const apolloProvider = createMockApollo([[getAgentQuery, agentQueryResponse]]);
wrapper = shallowMount(ClusterAgentShow, { wrapper = extendedWrapper(
shallowMount(ClusterAgentShow, {
localVue, localVue,
apolloProvider, apolloProvider,
propsData, propsData,
stubs: { GlSprintf, TimeAgoTooltip, GlTab }, stubs: { GlSprintf, TimeAgoTooltip, GlTab },
}); }),
);
}; };
const createWrapperWithoutApollo = ({ clusterAgent, loading = false }) => { const createWrapperWithoutApollo = ({ clusterAgent, loading = false, slots = {} }) => {
const $apollo = { queries: { clusterAgent: { loading } } }; const $apollo = { queries: { clusterAgent: { loading } } };
wrapper = shallowMount(ClusterAgentShow, { wrapper = extendedWrapper(
shallowMount(ClusterAgentShow, {
propsData, propsData,
mocks: { $apollo, clusterAgent }, mocks: { $apollo, clusterAgent },
slots,
stubs: { GlTab }, stubs: { GlTab },
}); }),
);
}; };
const findCreatedText = () => wrapper.find('[data-testid="cluster-agent-create-info"]').text(); const findCreatedText = () => wrapper.findByTestId('cluster-agent-create-info').text();
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findPaginationButtons = () => wrapper.find(GlKeysetPagination); const findPaginationButtons = () => wrapper.findComponent(GlKeysetPagination);
const findTokenCount = () => wrapper.find('[data-testid="cluster-agent-token-count"]').text(); const findTokenCount = () => wrapper.findByTestId('cluster-agent-token-count').text();
const findEESecurityTabSlot = () => wrapper.findByTestId('ee-security-tab');
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -87,7 +95,7 @@ describe('ClusterAgentShow', () => { ...@@ -87,7 +95,7 @@ describe('ClusterAgentShow', () => {
}); });
it('renders token table', () => { it('renders token table', () => {
expect(wrapper.find(TokenTable).exists()).toBe(true); expect(wrapper.findComponent(TokenTable).exists()).toBe(true);
}); });
it('should not render pagination buttons when there are no additional pages', () => { it('should not render pagination buttons when there are no additional pages', () => {
...@@ -188,8 +196,27 @@ describe('ClusterAgentShow', () => { ...@@ -188,8 +196,27 @@ describe('ClusterAgentShow', () => {
}); });
it('displays an alert message', () => { it('displays an alert message', () => {
expect(wrapper.find(GlAlert).exists()).toBe(true); expect(wrapper.findComponent(GlAlert).exists()).toBe(true);
expect(wrapper.text()).toContain(ClusterAgentShow.i18n.loadingError); expect(wrapper.text()).toContain(ClusterAgentShow.i18n.loadingError);
}); });
}); });
describe('ee-security-tab slot', () => {
it('does not display when a slot is not passed in', async () => {
createWrapperWithoutApollo({ clusterAgent: defaultClusterAgent });
await nextTick();
expect(findEESecurityTabSlot().exists()).toBe(false);
});
it('does display when a slot is passed in', async () => {
createWrapperWithoutApollo({
clusterAgent: defaultClusterAgent,
slots: {
'ee-security-tab': `<gl-tab data-testid="ee-security-tab">Security Tab!</gl-tab>`,
},
});
await nextTick();
expect(findEESecurityTabSlot().exists()).toBe(true);
});
});
}); });
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