Commit 37c1ed7b authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch 'disable-certificate-clusters-frontend' into 'master'

Remove legacy clusters UI when :certificate_based_clusters is disabled

See merge request gitlab-org/gitlab!82472
parents 9ee95e83 dffb7edd
<script>
import {
GlButton,
GlDropdown,
GlDropdownItem,
GlModalDirective,
......@@ -14,6 +15,7 @@ export default {
i18n: CLUSTERS_ACTIONS,
INSTALL_AGENT_MODAL_ID,
components: {
GlButton,
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
......@@ -23,7 +25,13 @@ export default {
GlModalDirective,
GlTooltip: GlTooltipDirective,
},
inject: ['newClusterPath', 'addClusterPath', 'canAddCluster', 'displayClusterAgents'],
inject: [
'newClusterPath',
'addClusterPath',
'canAddCluster',
'displayClusterAgents',
'certificateBasedClustersEnabled',
],
computed: {
tooltip() {
const { connectWithAgent, connectExistingCluster, dropdownDisabledHint } = this.$options.i18n;
......@@ -46,6 +54,7 @@ export default {
<template>
<div class="nav-controls gl-ml-auto">
<gl-dropdown
v-if="certificateBasedClustersEnabled"
ref="dropdown"
v-gl-modal-directive="shouldTriggerModal && $options.INSTALL_AGENT_MODAL_ID"
v-gl-tooltip="tooltip"
......@@ -75,5 +84,15 @@ export default {
{{ $options.i18n.connectExistingCluster }}
</gl-dropdown-item>
</gl-dropdown>
<gl-button
v-else
v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
v-gl-tooltip="tooltip"
:disabled="!canAddCluster"
category="primary"
variant="confirm"
>
{{ $options.i18n.connectWithAgent }}
</gl-button>
</div>
</template>
......@@ -9,6 +9,7 @@ import {
AGENT,
EVENT_LABEL_TABS,
EVENT_ACTIONS_CHANGE,
AGENT_TAB,
} from '../constants';
import Agents from './agents.vue';
import InstallAgentModal from './install_agent_modal.vue';
......@@ -28,9 +29,8 @@ export default {
Agents,
InstallAgentModal,
},
CLUSTERS_TABS,
mixins: [trackingMixin],
inject: ['displayClusterAgents'],
inject: ['displayClusterAgents', 'certificateBasedClustersEnabled'],
props: {
defaultBranchName: {
default: '.noBranch',
......@@ -45,21 +45,27 @@ export default {
};
},
computed: {
clusterTabs() {
return this.displayClusterAgents ? CLUSTERS_TABS : [CERTIFICATE_TAB];
availableTabs() {
const clusterTabs = this.displayClusterAgents ? CLUSTERS_TABS : [CERTIFICATE_TAB];
return this.certificateBasedClustersEnabled ? clusterTabs : [AGENT_TAB];
},
},
watch: {
selectedTabIndex(val) {
this.onTabChange(val);
selectedTabIndex: {
handler(val) {
this.onTabChange(val);
},
immediate: true,
},
},
methods: {
setSelectedTab(tabName) {
this.selectedTabIndex = this.clusterTabs.findIndex((tab) => tab.queryParamValue === tabName);
this.selectedTabIndex = this.availableTabs.findIndex(
(tab) => tab.queryParamValue === tabName,
);
},
onTabChange(tab) {
const tabName = this.clusterTabs[tab].queryParamValue;
const tabName = this.availableTabs[tab].queryParamValue;
this.maxAgents = tabName === AGENT ? MAX_LIST_COUNT : MAX_CLUSTERS_LIST;
this.track(EVENT_ACTIONS_CHANGE, { property: tabName });
......@@ -76,7 +82,7 @@ export default {
lazy
>
<gl-tab
v-for="(tab, idx) in clusterTabs"
v-for="(tab, idx) in availableTabs"
:key="idx"
:title="tab.title"
:query-param-value="tab.queryParamValue"
......
......@@ -232,25 +232,24 @@ export const CERTIFICATE_BASED_CARD_INFO = {
export const MAX_CLUSTERS_LIST = 6;
export const ALL_TAB = {
title: s__('ClusterAgents|All'),
component: 'ClustersViewAll',
queryParamValue: 'all',
};
export const AGENT_TAB = {
title: s__('ClusterAgents|Agent'),
component: 'agents',
queryParamValue: 'agent',
};
export const CERTIFICATE_TAB = {
title: s__('ClusterAgents|Certificate'),
component: 'clusters',
queryParamValue: 'certificate_based',
};
export const CLUSTERS_TABS = [
{
title: s__('ClusterAgents|All'),
component: 'ClustersViewAll',
queryParamValue: 'all',
},
{
title: s__('ClusterAgents|Agent'),
component: 'agents',
queryParamValue: 'agent',
},
CERTIFICATE_TAB,
];
export const CLUSTERS_TABS = [ALL_TAB, AGENT_TAB, CERTIFICATE_TAB];
export const CLUSTERS_ACTIONS = {
actionsButton: s__('ClusterAgents|Actions'),
......
......@@ -31,6 +31,7 @@ export default () => {
canAdminCluster,
gitlabVersion,
displayClusterAgents,
certificateBasedClustersEnabled,
} = el.dataset;
return new Vue({
......@@ -48,6 +49,7 @@ export default () => {
canAdminCluster: parseBoolean(canAdminCluster),
gitlabVersion,
displayClusterAgents: parseBoolean(displayClusterAgents),
certificateBasedClustersEnabled: parseBoolean(certificateBasedClustersEnabled),
},
store: createStore(el.dataset),
render(createElement) {
......
......@@ -2,6 +2,7 @@
class Admin::ClustersController < Clusters::ClustersController
include EnforcesAdminAuthentication
before_action :ensure_feature_enabled!
layout 'admin'
......
......@@ -3,6 +3,7 @@
class Groups::ClustersController < Clusters::ClustersController
include ControllerWithCrossProjectAccessCheck
before_action :ensure_feature_enabled!
prepend_before_action :group
requires_cross_project_access
......
......@@ -465,7 +465,10 @@ module ApplicationSettingsHelper
end
def instance_clusters_enabled?
can?(current_user, :read_cluster, Clusters::Instance.new)
clusterable = Clusters::Instance.new
Feature.enabled?(:certificate_based_clusters, clusterable, default_enabled: :yaml, type: :ops) &&
can?(current_user, :read_cluster, clusterable)
end
def omnibus_protected_paths_throttle?
......
......@@ -31,7 +31,8 @@ module ClustersHelper
add_cluster_path: clusterable.new_path(tab: 'add'),
can_add_cluster: clusterable.can_add_cluster?.to_s,
can_admin_cluster: clusterable.can_admin_cluster?.to_s,
display_cluster_agents: display_cluster_agents?(clusterable).to_s
display_cluster_agents: display_cluster_agents?(clusterable).to_s,
certificate_based_clusters_enabled: Feature.enabled?(:certificate_based_clusters, clusterable, default_enabled: :yaml, type: :ops).to_s
}
end
......
......@@ -21,7 +21,10 @@ module Sidebars
override :render?
def render?
can?(context.current_user, :read_cluster, context.group)
clusterable = context.group
Feature.enabled?(:certificate_based_clusters, clusterable, default_enabled: :yaml, type: :ops) &&
can?(context.current_user, :read_cluster, clusterable)
end
override :extra_container_html_options
......
......@@ -27,7 +27,7 @@ RSpec.describe Admin::ClustersController do
create(:cluster, :disabled, :provided_by_gcp, :production_environment, :instance)
end
include_examples ':certificate_based_clusters feature flag index responses' do
include_examples ':certificate_based_clusters feature flag controller responses' do
let(:subject) { get_index }
end
......
......@@ -32,7 +32,7 @@ RSpec.describe Groups::ClustersController do
create(:cluster, :disabled, :provided_by_gcp, :production_environment, cluster_type: :group_type, groups: [group])
end
include_examples ':certificate_based_clusters feature flag index responses' do
include_examples ':certificate_based_clusters feature flag controller responses' do
let(:subject) { go }
end
......
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlButton } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ClustersActions from '~/clusters_list/components/clusters_actions.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
......@@ -15,6 +15,7 @@ describe('ClustersActionsComponent', () => {
addClusterPath,
canAddCluster: true,
displayClusterAgents: true,
certificateBasedClustersEnabled: true,
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
......@@ -24,6 +25,7 @@ describe('ClustersActionsComponent', () => {
const findNewClusterLink = () => wrapper.findByTestId('new-cluster-link');
const findConnectClusterLink = () => wrapper.findByTestId('connect-cluster-link');
const findConnectNewAgentLink = () => wrapper.findByTestId('connect-new-agent-link');
const findConnectWithAgentButton = () => wrapper.findComponent(GlButton);
const createWrapper = (provideData = {}) => {
wrapper = shallowMountExtended(ClustersActions, {
......@@ -45,90 +47,110 @@ describe('ClustersActionsComponent', () => {
afterEach(() => {
wrapper.destroy();
});
describe('when the certificate based clusters are enabled', () => {
it('renders actions menu', () => {
expect(findDropdown().props('text')).toBe(CLUSTERS_ACTIONS.actionsButton);
});
it('renders actions menu', () => {
expect(findDropdown().props('text')).toBe(CLUSTERS_ACTIONS.actionsButton);
});
it('renders correct href attributes for the links', () => {
expect(findNewClusterLink().attributes('href')).toBe(newClusterPath);
expect(findConnectClusterLink().attributes('href')).toBe(addClusterPath);
});
it('renders correct href attributes for the links', () => {
expect(findNewClusterLink().attributes('href')).toBe(newClusterPath);
expect(findConnectClusterLink().attributes('href')).toBe(addClusterPath);
});
describe('when user cannot add clusters', () => {
beforeEach(() => {
createWrapper({ canAddCluster: false });
});
describe('when user cannot add clusters', () => {
beforeEach(() => {
createWrapper({ canAddCluster: false });
});
it('disables dropdown', () => {
expect(findDropdown().props('disabled')).toBe(true);
});
it('disables dropdown', () => {
expect(findDropdown().props('disabled')).toBe(true);
});
it('shows tooltip explaining why dropdown is disabled', () => {
const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
expect(tooltip.value).toBe(CLUSTERS_ACTIONS.dropdownDisabledHint);
});
it('does not bind split dropdown button', () => {
const binding = getBinding(findDropdown().element, 'gl-modal-directive');
it('shows tooltip explaining why dropdown is disabled', () => {
const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
expect(tooltip.value).toBe(CLUSTERS_ACTIONS.dropdownDisabledHint);
expect(binding.value).toBe(false);
});
});
it('does not bind split dropdown button', () => {
const binding = getBinding(findDropdown().element, 'gl-modal-directive');
describe('when on project level', () => {
it('renders a dropdown with 3 actions items', () => {
expect(findDropdownItemIds()).toEqual([
'connect-new-agent-link',
'new-cluster-link',
'connect-cluster-link',
]);
});
expect(binding.value).toBe(false);
});
});
it('renders correct modal id for the agent link', () => {
const binding = getBinding(findConnectNewAgentLink().element, 'gl-modal-directive');
describe('when on project level', () => {
it('renders a dropdown with 3 actions items', () => {
expect(findDropdownItemIds()).toEqual([
'connect-new-agent-link',
'new-cluster-link',
'connect-cluster-link',
]);
});
expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
});
it('renders correct modal id for the agent link', () => {
const binding = getBinding(findConnectNewAgentLink().element, 'gl-modal-directive');
it('shows tooltip', () => {
const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
expect(tooltip.value).toBe(CLUSTERS_ACTIONS.connectWithAgent);
});
expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
});
it('shows split button in dropdown', () => {
expect(findDropdown().props('split')).toBe(true);
});
it('shows tooltip', () => {
const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
expect(tooltip.value).toBe(CLUSTERS_ACTIONS.connectWithAgent);
});
it('binds split button with modal id', () => {
const binding = getBinding(findDropdown().element, 'gl-modal-directive');
it('shows split button in dropdown', () => {
expect(findDropdown().props('split')).toBe(true);
expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
});
});
it('binds split button with modal id', () => {
const binding = getBinding(findDropdown().element, 'gl-modal-directive');
describe('when on group or admin level', () => {
beforeEach(() => {
createWrapper({ displayClusterAgents: false });
});
expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
});
});
it('renders a dropdown with 2 actions items', () => {
expect(findDropdownItemIds()).toEqual(['new-cluster-link', 'connect-cluster-link']);
});
describe('when on group or admin level', () => {
beforeEach(() => {
createWrapper({ displayClusterAgents: false });
});
it('shows tooltip', () => {
const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
expect(tooltip.value).toBe(CLUSTERS_ACTIONS.connectExistingCluster);
});
it('renders a dropdown with 2 actions items', () => {
expect(findDropdownItemIds()).toEqual(['new-cluster-link', 'connect-cluster-link']);
});
it('does not show split button in dropdown', () => {
expect(findDropdown().props('split')).toBe(false);
});
it('does not bind dropdown button to modal', () => {
const binding = getBinding(findDropdown().element, 'gl-modal-directive');
it('shows tooltip', () => {
const tooltip = getBinding(findDropdown().element, 'gl-tooltip');
expect(tooltip.value).toBe(CLUSTERS_ACTIONS.connectExistingCluster);
expect(binding.value).toBe(false);
});
});
});
it('does not show split button in dropdown', () => {
expect(findDropdown().props('split')).toBe(false);
describe('when the certificate based clusters not enabled', () => {
beforeEach(() => {
createWrapper({ certificateBasedClustersEnabled: false });
});
it('does not bind dropdown button to modal', () => {
const binding = getBinding(findDropdown().element, 'gl-modal-directive');
it('it does not show the the dropdown', () => {
expect(findDropdown().exists()).toBe(false);
});
expect(binding.value).toBe(false);
it('shows the connect with agent button', () => {
expect(findConnectWithAgentButton().props()).toMatchObject({
disabled: !defaultProvide.canAddCluster,
category: 'primary',
variant: 'confirm',
});
expect(findConnectWithAgentButton().text()).toBe(CLUSTERS_ACTIONS.connectWithAgent);
});
});
});
......@@ -6,6 +6,7 @@ import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vu
import {
AGENT,
CERTIFICATE_BASED,
AGENT_TAB,
CLUSTERS_TABS,
CERTIFICATE_TAB,
MAX_CLUSTERS_LIST,
......@@ -24,10 +25,18 @@ describe('ClustersMainViewComponent', () => {
defaultBranchName,
};
const createWrapper = ({ displayClusterAgents }) => {
const defaultProvide = {
certificateBasedClustersEnabled: true,
displayClusterAgents: true,
};
const createWrapper = (extendedProvide = {}) => {
wrapper = shallowMountExtended(ClustersMainView, {
propsData,
provide: { displayClusterAgents },
provide: {
...defaultProvide,
...extendedProvide,
},
});
};
......@@ -40,91 +49,111 @@ describe('ClustersMainViewComponent', () => {
const findGlTabAtIndex = (index) => findAllTabs().at(index);
const findComponent = () => wrapper.findByTestId('clusters-tab-component');
const findModal = () => wrapper.findComponent(InstallAgentModal);
describe('when the certificate based clusters are enabled', () => {
describe('when on project level', () => {
beforeEach(() => {
createWrapper({ displayClusterAgents: true });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
describe('when on project level', () => {
beforeEach(() => {
createWrapper({ displayClusterAgents: true });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
});
it('renders `GlTabs` with `syncActiveTabWithQueryParams` and `queryParamName` props set', () => {
expect(findTabs().exists()).toBe(true);
expect(findTabs().props('syncActiveTabWithQueryParams')).toBe(true);
});
it('renders `GlTabs` with `syncActiveTabWithQueryParams` and `queryParamName` props set', () => {
expect(findTabs().exists()).toBe(true);
expect(findTabs().props('syncActiveTabWithQueryParams')).toBe(true);
});
it('renders correct number of tabs', () => {
expect(findAllTabs()).toHaveLength(CLUSTERS_TABS.length);
});
it('renders correct number of tabs', () => {
expect(findAllTabs()).toHaveLength(CLUSTERS_TABS.length);
});
describe('tabs', () => {
it.each`
tabTitle | queryParamValue | lineNumber
${'All'} | ${'all'} | ${0}
${'Agent'} | ${AGENT} | ${1}
${'Certificate'} | ${CERTIFICATE_BASED} | ${2}
`(
'renders correct tab title and query param value',
({ tabTitle, queryParamValue, lineNumber }) => {
expect(findGlTabAtIndex(lineNumber).attributes('title')).toBe(tabTitle);
expect(findGlTabAtIndex(lineNumber).props('queryParamValue')).toBe(queryParamValue);
},
);
});
describe('tabs', () => {
it.each`
tabTitle | queryParamValue | lineNumber
${'All'} | ${'all'} | ${0}
${'Agent'} | ${AGENT} | ${1}
${'Certificate'} | ${CERTIFICATE_BASED} | ${2}
describe.each`
tab | tabName
${'1'} | ${AGENT}
${'2'} | ${CERTIFICATE_BASED}
`(
'renders correct tab title and query param value',
({ tabTitle, queryParamValue, lineNumber }) => {
expect(findGlTabAtIndex(lineNumber).attributes('title')).toBe(tabTitle);
expect(findGlTabAtIndex(lineNumber).props('queryParamValue')).toBe(queryParamValue);
'when the child component emits the tab change event for $tabName tab',
({ tab, tabName }) => {
beforeEach(() => {
findComponent().vm.$emit('changeTab', tabName);
});
it(`changes the tab value to ${tab}`, () => {
expect(findTabs().attributes('value')).toBe(tab);
});
},
);
});
describe.each`
tab | tabName
${'1'} | ${AGENT}
${'2'} | ${CERTIFICATE_BASED}
`(
'when the child component emits the tab change event for $tabName tab',
({ tab, tabName }) => {
describe.each`
tab | tabName | maxAgents
${1} | ${AGENT} | ${MAX_LIST_COUNT}
${2} | ${CERTIFICATE_BASED} | ${MAX_CLUSTERS_LIST}
`('when the active tab is $tabName', ({ tab, tabName, maxAgents }) => {
beforeEach(() => {
findComponent().vm.$emit('changeTab', tabName);
findTabs().vm.$emit('input', tab);
});
it(`changes the tab value to ${tab}`, () => {
expect(findTabs().attributes('value')).toBe(tab);
it('passes child-component param to the component', () => {
expect(findComponent().props('defaultBranchName')).toBe(defaultBranchName);
});
},
);
describe.each`
tab | tabName | maxAgents
${1} | ${AGENT} | ${MAX_LIST_COUNT}
${2} | ${CERTIFICATE_BASED} | ${MAX_CLUSTERS_LIST}
`('when the active tab is $tabName', ({ tab, tabName, maxAgents }) => {
beforeEach(() => {
findTabs().vm.$emit('input', tab);
it(`sets max-agents param to ${maxAgents} and passes it to the modal`, () => {
expect(findModal().props('maxAgents')).toBe(maxAgents);
});
it(`sends the correct tracking event with the property '${tabName}'`, () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_CHANGE, {
label: EVENT_LABEL_TABS,
property: tabName,
});
});
});
});
it('passes child-component param to the component', () => {
expect(findComponent().props('defaultBranchName')).toBe(defaultBranchName);
describe('when on group or admin level', () => {
beforeEach(() => {
createWrapper({ displayClusterAgents: false });
});
it(`sets max-agents param to ${maxAgents} and passes it to the modal`, () => {
expect(findModal().props('maxAgents')).toBe(maxAgents);
it('renders correct number of tabs', () => {
expect(findAllTabs()).toHaveLength(1);
});
it(`sends the correct tracking event with the property '${tabName}'`, () => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_CHANGE, {
label: EVENT_LABEL_TABS,
property: tabName,
});
it('renders correct tab title', () => {
expect(findGlTabAtIndex(0).attributes('title')).toBe(CERTIFICATE_TAB.title);
});
});
});
describe('when on group or admin level', () => {
beforeEach(() => {
createWrapper({ displayClusterAgents: false });
});
describe('when the certificate based clusters not enabled', () => {
beforeEach(() => {
createWrapper({ certificateBasedClustersEnabled: false });
});
it('renders correct number of tabs', () => {
expect(findAllTabs()).toHaveLength(1);
});
it('it displays only the Agent tab', () => {
expect(findAllTabs()).toHaveLength(1);
const agentTab = findGlTabAtIndex(0);
it('renders correct tab title', () => {
expect(findGlTabAtIndex(0).attributes('title')).toBe(CERTIFICATE_TAB.title);
expect(agentTab.props()).toMatchObject({
queryParamValue: AGENT_TAB.queryParamValue,
titleLinkClass: '',
});
expect(agentTab.attributes()).toMatchObject({
title: AGENT_TAB.title,
});
});
});
});
});
......@@ -293,4 +293,25 @@ RSpec.describe ApplicationSettingsHelper do
it { is_expected.to eq([%w(Track track), %w(Compress compress)]) }
end
describe '#instance_clusters_enabled?' do
let_it_be(:user) { create(:user) }
subject { helper.instance_clusters_enabled? }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).with(user, :read_cluster, instance_of(Clusters::Instance)).and_return(true)
end
it { is_expected.to be_truthy}
context ':certificate_based_clusters feature flag is disabled' do
before do
stub_feature_flags(certificate_based_clusters: false)
end
it { is_expected.to be_falsey }
end
end
end
......@@ -136,6 +136,28 @@ RSpec.describe ClustersHelper do
expect(subject[:display_cluster_agents]).to eq("false")
end
end
describe 'certificate based clusters enabled' do
before do
stub_feature_flags(certificate_based_clusters: flag_enabled)
end
context 'feature flag is enabled' do
let(:flag_enabled) { true }
it do
expect(subject[:certificate_based_clusters_enabled]).to eq('true')
end
end
context 'feature flag is disabled' do
let(:flag_enabled) { false }
it do
expect(subject[:certificate_based_clusters_enabled]).to eq('false')
end
end
end
end
describe '#js_clusters_data' do
......
......@@ -28,5 +28,15 @@ RSpec.describe Sidebars::Groups::Menus::KubernetesMenu do
expect(menu.render?).to eq false
end
end
context ':certificate_based_clusters feature flag is disabled' do
before do
stub_feature_flags(certificate_based_clusters: false)
end
it 'returns false' do
expect(menu.render?).to eq false
end
end
end
end
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