From da9ffc3ac0cab7f97f648164c33c756a77da34cd Mon Sep 17 00:00:00 2001 From: anna_vovchenko Date: Wed, 3 Nov 2021 23:28:25 +0200 Subject: [PATCH 01/12] Move nav and actions on clusters page to Vue As we want enhance the GitLab Agent, we are changing the nav: - moving it to Vue - switching tabs order - adding persistent actions button that defaults to register new Agent Changelog: changed --- .../clusters_list/components/agents.vue | 11 +++++++ .../components/install_agent_modal.vue | 2 ++ .../clusters_list/store/actions.js | 4 +++ .../clusters_list/store/mutation_types.js | 1 + .../clusters_list/store/mutations.js | 3 ++ .../javascripts/clusters_list/store/state.js | 1 + .../clusters_list/components/agents_spec.js | 32 +++++++++++++++++++ .../components/install_agent_modal_spec.js | 21 ++++++++++++ .../clusters_list/store/mutations_spec.js | 8 +++++ 9 files changed, 83 insertions(+) diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue index 4fc79b32b9cdfa..4097968a32de11 100644 --- a/app/assets/javascripts/clusters_list/components/agents.vue +++ b/app/assets/javascripts/clusters_list/components/agents.vue @@ -1,5 +1,6 @@ diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue index 000730ac1ba526..af9ee309bfb06a 100644 --- a/app/assets/javascripts/clusters_list/components/agent_table.vue +++ b/app/assets/javascripts/clusters_list/components/agent_table.vue @@ -9,6 +9,7 @@ import { GlPopover, } from '@gitlab/ui'; import { s__ } from '~/locale'; +import { helpPagePath } from '~/helpers/help_page_helper'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import { helpPagePath } from '~/helpers/help_page_helper'; @@ -67,6 +68,11 @@ export default { }, ]; }, + integrationDocsUrl() { + return helpPagePath('user/clusters/agent/index', { + anchor: 'get-started-with-gitops-and-the-gitlab-agent', + }); + }, }, methods: { getCellId(item) { diff --git a/spec/frontend/clusters_list/components/agent_empty_state_spec.js b/spec/frontend/clusters_list/components/agent_empty_state_spec.js index 38f0e0ba2c4f5a..87ba4f30ffcfcc 100644 --- a/spec/frontend/clusters_list/components/agent_empty_state_spec.js +++ b/spec/frontend/clusters_list/components/agent_empty_state_spec.js @@ -19,12 +19,6 @@ describe('AgentEmptyStateComponent', () => { projectPath, }; - const findConfigurationsAlert = () => wrapper.findComponent(GlAlert); - const findMultipleClustersDocsLink = () => wrapper.findByTestId('multiple-clusters-docs-link'); - const findInstallDocsLink = () => wrapper.findByTestId('install-docs-link'); - const findIntegrationButton = () => wrapper.findByTestId('integration-primary-button'); - const findEmptyState = () => wrapper.findComponent(GlEmptyState); - beforeEach(() => { wrapper = shallowMountExtended(AgentEmptyState, { propsData, diff --git a/spec/frontend/clusters_list/components/agents_spec.js b/spec/frontend/clusters_list/components/agents_spec.js index 3c069ff96851b5..a516b8769f1c0e 100644 --- a/spec/frontend/clusters_list/components/agents_spec.js +++ b/spec/frontend/clusters_list/components/agents_spec.js @@ -266,16 +266,16 @@ describe('Agents', () => { }); describe('when new agent is registered', () => { - let reloadAgentsSpy; + let refetchAgentsSpy; beforeEach(() => { createWrapper({ agents: [] }); - reloadAgentsSpy = jest.spyOn(wrapper.vm, 'reloadAgents'); + refetchAgentsSpy = jest.spyOn(wrapper.vm.$apollo.queries.agents, 'refetch'); }); it('reloads agents query', async () => { wrapper.vm.$store.state.newAgentRegistered = true; await wrapper.vm.$nextTick(); - expect(reloadAgentsSpy).toHaveBeenCalled(); + expect(refetchAgentsSpy).toHaveBeenCalled(); }); }); }); -- GitLab From 742089d93662ff6cab64d7a23ce16736c0679e53 Mon Sep 17 00:00:00 2001 From: anna_vovchenko Date: Tue, 9 Nov 2021 20:40:37 +0200 Subject: [PATCH 03/12] Applied suggestions after the FE review --- .../components/agent_empty_state.vue | 16 ---------------- .../components/agent_empty_state_spec.js | 6 ++++++ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/clusters_list/components/agent_empty_state.vue b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue index d15452cd791ef2..2327d702b09c35 100644 --- a/app/assets/javascripts/clusters_list/components/agent_empty_state.vue +++ b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue @@ -36,22 +36,6 @@ export default { repositoryPath() { return `/${this.projectPath}`; }, - agentDocsUrl() { - return helpPagePath('user/clusters/agent/index'); - }, - installDocsUrl() { - return helpPagePath('administration/clusters/kas'); - }, - getStartedDocsUrl() { - return helpPagePath('user/clusters/agent/index', { - anchor: 'define-a-configuration-repository', - }); - }, - integrationsDocsUrl() { - return helpPagePath('user/clusters/agent/index', { - anchor: 'get-started-with-gitops-and-the-gitlab-agent', - }); - }, }, }; diff --git a/spec/frontend/clusters_list/components/agent_empty_state_spec.js b/spec/frontend/clusters_list/components/agent_empty_state_spec.js index 87ba4f30ffcfcc..38f0e0ba2c4f5a 100644 --- a/spec/frontend/clusters_list/components/agent_empty_state_spec.js +++ b/spec/frontend/clusters_list/components/agent_empty_state_spec.js @@ -19,6 +19,12 @@ describe('AgentEmptyStateComponent', () => { projectPath, }; + const findConfigurationsAlert = () => wrapper.findComponent(GlAlert); + const findMultipleClustersDocsLink = () => wrapper.findByTestId('multiple-clusters-docs-link'); + const findInstallDocsLink = () => wrapper.findByTestId('install-docs-link'); + const findIntegrationButton = () => wrapper.findByTestId('integration-primary-button'); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + beforeEach(() => { wrapper = shallowMountExtended(AgentEmptyState, { propsData, -- GitLab From 98155ed83956e773dfc42977b3f2f157a26b1c4c Mon Sep 17 00:00:00 2001 From: anna_vovchenko Date: Tue, 9 Nov 2021 23:17:59 +0200 Subject: [PATCH 04/12] Updated branch with the latest changes --- .../clusters_list/components/agent_empty_state.vue | 1 - .../javascripts/clusters_list/components/agent_table.vue | 6 ------ 2 files changed, 7 deletions(-) diff --git a/app/assets/javascripts/clusters_list/components/agent_empty_state.vue b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue index 2327d702b09c35..5c6f9de0624ce2 100644 --- a/app/assets/javascripts/clusters_list/components/agent_empty_state.vue +++ b/app/assets/javascripts/clusters_list/components/agent_empty_state.vue @@ -2,7 +2,6 @@ import { GlButton, GlEmptyState, GlLink, GlSprintf, GlAlert, GlModalDirective } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; import { INSTALL_AGENT_MODAL_ID, I18N_AGENTS_EMPTY_STATE } from '../constants'; -import { helpPagePath } from '~/helpers/help_page_helper'; export default { i18n: I18N_AGENTS_EMPTY_STATE, diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue index af9ee309bfb06a..000730ac1ba526 100644 --- a/app/assets/javascripts/clusters_list/components/agent_table.vue +++ b/app/assets/javascripts/clusters_list/components/agent_table.vue @@ -9,7 +9,6 @@ import { GlPopover, } from '@gitlab/ui'; import { s__ } from '~/locale'; -import { helpPagePath } from '~/helpers/help_page_helper'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import { helpPagePath } from '~/helpers/help_page_helper'; @@ -68,11 +67,6 @@ export default { }, ]; }, - integrationDocsUrl() { - return helpPagePath('user/clusters/agent/index', { - anchor: 'get-started-with-gitops-and-the-gitlab-agent', - }); - }, }, methods: { getCellId(item) { -- GitLab From 6bb5e86a691d9c268c0ba6b526d021fb96e0dc4f Mon Sep 17 00:00:00 2001 From: anna_vovchenko Date: Thu, 11 Nov 2021 01:01:01 +0200 Subject: [PATCH 05/12] Changed setNewAgent action to apollo cache --- .../clusters_list/components/agents.vue | 11 ------- .../components/install_agent_modal.vue | 2 -- .../clusters_list/store/actions.js | 4 --- .../clusters_list/store/mutation_types.js | 1 - .../clusters_list/store/mutations.js | 3 -- .../javascripts/clusters_list/store/state.js | 1 - .../clusters_list/components/agents_spec.js | 32 ------------------- .../components/install_agent_modal_spec.js | 21 ------------ .../clusters_list/store/mutations_spec.js | 8 ----- 9 files changed, 83 deletions(-) diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue index 4097968a32de11..4fc79b32b9cdfa 100644 --- a/app/assets/javascripts/clusters_list/components/agents.vue +++ b/app/assets/javascripts/clusters_list/components/agents.vue @@ -1,6 +1,5 @@ @@ -144,7 +161,11 @@ export default { - + diff --git a/app/assets/javascripts/clusters_list/components/clusters.vue b/app/assets/javascripts/clusters_list/components/clusters.vue index bc27563b6d54f7..e2d073a8c854aa 100644 --- a/app/assets/javascripts/clusters_list/components/clusters.vue +++ b/app/assets/javascripts/clusters_list/components/clusters.vue @@ -34,6 +34,18 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + props: { + childComponent: { + default: false, + required: false, + type: Boolean, + }, + limit: { + default: null, + required: false, + type: Number, + }, + }, computed: { ...mapState([ 'clusters', @@ -100,10 +112,14 @@ export default { }, }, mounted() { + if (this.limit) { + this.setClustersPerPage(this.limit); + } + this.fetchClusters(); }, methods: { - ...mapActions(['fetchClusters', 'reportSentryError', 'setPage']), + ...mapActions(['fetchClusters', 'reportSentryError', 'setPage', 'setClustersPerPage']), k8sQuantityToGb(quantity) { if (!quantity) { return 0; @@ -312,10 +328,10 @@ export default { - + +import { + GlCard, + GlSprintf, + GlPopover, + GlLink, + GlButton, + GlLoadingIcon, + GlModalDirective, +} from '@gitlab/ui'; +import { mapState } from 'vuex'; +import { sprintf } from '~/locale'; +import { + AGENT_CARD_INFO, + CERTIFICATE_BASED_CARD_INFO, + MAX_CLUSTERS_LIST, + INSTALL_AGENT_MODAL_ID, +} from '../constants'; +import Clusters from './clusters.vue'; +import Agents from './agents.vue'; + +export default { + components: { + GlCard, + GlSprintf, + GlPopover, + GlLink, + GlButton, + GlLoadingIcon, + Clusters, + Agents, + }, + directives: { + GlModalDirective, + }, + AGENT_CARD_INFO, + CERTIFICATE_BASED_CARD_INFO, + MAX_CLUSTERS_LIST, + INSTALL_AGENT_MODAL_ID, + inject: ['addClusterPath'], + props: { + defaultBranchName: { + default: '.noBranch', + required: false, + type: String, + }, + }, + data() { + return { + loadingAgents: true, + totalAgents: null, + }; + }, + computed: { + ...mapState(['loadingClusters', 'totalClusters']), + isLoading() { + return this.loadingAgents || this.loadingClusters; + }, + }, + methods: { + cardTitle(card) { + let clustersNumber; + let title; + let emptyTitle; + let cardTitle; + + if (card === 'agent') { + clustersNumber = this.totalAgents; + title = AGENT_CARD_INFO.title; + emptyTitle = AGENT_CARD_INFO.emptyTitle; + } else if (card === 'certificate_based') { + clustersNumber = this.totalClusters; + title = CERTIFICATE_BASED_CARD_INFO.title; + emptyTitle = CERTIFICATE_BASED_CARD_INFO.emptyTitle; + } + if (clustersNumber > 0) { + cardTitle = sprintf(title, { + number: clustersNumber < MAX_CLUSTERS_LIST ? clustersNumber : MAX_CLUSTERS_LIST, + total: clustersNumber, + }); + } else { + cardTitle = emptyTitle; + } + return cardTitle; + }, + cardFooterNumber(number) { + return number > MAX_CLUSTERS_LIST ? number : ''; + }, + onAgentsLoad(number) { + this.totalAgents = number; + this.loadingAgents = false; + }, + changeTab($event, tab) { + $event.preventDefault(); + this.$emit('changeTab', tab); + }, + }, +}; + + diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js index 36e6a065cbf07e..a83b8f8690ab60 100644 --- a/app/assets/javascripts/clusters_list/constants.js +++ b/app/assets/javascripts/clusters_list/constants.js @@ -1,4 +1,5 @@ import { __, s__, sprintf } from '~/locale'; +import { helpPagePath } from '~/helpers/help_page_helper'; export const MAX_LIST_COUNT = 25; export const INSTALL_AGENT_MODAL_ID = 'install-agent'; @@ -167,7 +168,40 @@ export const I18N_CLUSTERS_EMPTY_STATE = { buttonText: s__('ClusterIntegration|Connect with a certificate'), }; +export const AGENT_CARD_INFO = { + tabName: 'agent', + title: sprintf(s__('ClusterAgents|%{number} of %{total} Agent based integrations')), + emptyTitle: s__('ClusterAgents|No Agent based integrations'), + tooltip: { + label: s__('ClusterAgents|Recommended'), + title: s__('ClusterAgents|GitLab Agents'), + text: sprintf( + s__( + 'ClusterAgents|GitLab Agents provide an increased level of security when integrating with clusters. %{linkStart}Learn more about the GitLab Kubernetes Agent.%{linkEnd}', + ), + ), + link: helpPagePath('user/clusters/agent/index'), + }, + actionText: s__('ClusterAgents|Install new Agent'), + footerText: sprintf(s__('ClusterAgents|View all %{number} Agent based integrations')), +}; + +export const CERTIFICATE_BASED_CARD_INFO = { + tabName: 'certificate_based', + title: sprintf(s__('ClusterAgents|%{number} of %{total} Certificate based integrations')), + emptyTitle: s__('ClusterAgents|No Certificate based integrations'), + actionText: s__('ClusterAgents|Connect existing cluster'), + footerText: sprintf(s__('ClusterAgents|View all %{number} Certificate based integrations')), +}; + +export const MAX_CLUSTERS_LIST = 6; + export const CLUSTERS_TABS = [ + { + title: s__('ClusterAgents|All'), + component: 'ClustersViewAll', + queryParamValue: 'all', + }, { title: s__('ClusterAgents|Agent'), component: 'agents', diff --git a/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql b/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql index 397895b7d0153c..47b259888779b0 100644 --- a/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql +++ b/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql @@ -20,6 +20,8 @@ query getAgents( pageInfo { ...PageInfo } + + count } repository { diff --git a/app/assets/javascripts/clusters_list/store/actions.js b/app/assets/javascripts/clusters_list/store/actions.js index f0f96fd7872d06..d70b36e63bc9b0 100644 --- a/app/assets/javascripts/clusters_list/store/actions.js +++ b/app/assets/javascripts/clusters_list/store/actions.js @@ -30,7 +30,13 @@ export const fetchClusters = ({ state, commit, dispatch }) => { const poll = new Poll({ resource: { - fetchClusters: (paginatedEndPoint) => axios.get(paginatedEndPoint), + fetchClusters: (paginatedEndPoint) => + axios.get(paginatedEndPoint, { + params: { + page: state.page, + per_page: state.clustersPerPage, + }, + }), }, data: `${state.endpoint}?page=${state.page}`, method: 'fetchClusters', @@ -78,3 +84,7 @@ export const fetchClusters = ({ state, commit, dispatch }) => { export const setPage = ({ commit }, page) => { commit(types.SET_PAGE, page); }; + +export const setClustersPerPage = ({ commit }, limit) => { + commit(types.SET_CLUSTERS_PER_PAGE, limit); +}; diff --git a/app/assets/javascripts/clusters_list/store/mutation_types.js b/app/assets/javascripts/clusters_list/store/mutation_types.js index beb4388c93e3b6..e88d4c74761784 100644 --- a/app/assets/javascripts/clusters_list/store/mutation_types.js +++ b/app/assets/javascripts/clusters_list/store/mutation_types.js @@ -2,3 +2,4 @@ export const SET_CLUSTERS_DATA = 'SET_CLUSTERS_DATA'; export const SET_LOADING_CLUSTERS = 'SET_LOADING_CLUSTERS'; export const SET_LOADING_NODES = 'SET_LOADING_NODES'; export const SET_PAGE = 'SET_PAGE'; +export const SET_CLUSTERS_PER_PAGE = 'SET_CLUSTERS_PER_PAGE'; diff --git a/app/assets/javascripts/clusters_list/store/mutations.js b/app/assets/javascripts/clusters_list/store/mutations.js index e5c15ccbd6e4b2..93156c9200fbba 100644 --- a/app/assets/javascripts/clusters_list/store/mutations.js +++ b/app/assets/javascripts/clusters_list/store/mutations.js @@ -18,4 +18,7 @@ export default { [types.SET_PAGE](state, value) { state.page = Number(value) || 1; }, + [types.SET_CLUSTERS_PER_PAGE](state, value) { + state.clustersPerPage = Number(value) || 1; + }, }; diff --git a/app/assets/javascripts/clusters_list/store/state.js b/app/assets/javascripts/clusters_list/store/state.js index 3dcbf58c8d39e6..763d7389d0f22d 100644 --- a/app/assets/javascripts/clusters_list/store/state.js +++ b/app/assets/javascripts/clusters_list/store/state.js @@ -5,7 +5,7 @@ export default (initialState = {}) => ({ endpoint: initialState.endpoint, hasAncestorClusters: false, clusters: [], - clustersPerPage: 0, + clustersPerPage: 20, loadingClusters: true, loadingNodes: true, page: 1, diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index b450bca4f41032..299bb73191d538 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -21,6 +21,12 @@ } } + .button-recommended { + @include gl-text-white; + background-color: $purple-500; + border-radius: $border-radius-large; + } + .gl-card-body { @include media-breakpoint-up(sm) { @include gl-pt-2; diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a9bd64027dc6dc..6bc4147e77a1f8 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7318,6 +7318,12 @@ msgstr "" msgid "Cluster type must be specificed for Stages::ClusterEndpointInserter" msgstr "" +msgid "ClusterAgents|%{number} of %{total} Agent based integrations" +msgstr "" + +msgid "ClusterAgents|%{number} of %{total} Certificate based integrations" +msgstr "" + msgid "ClusterAgents|Access tokens" msgstr "" @@ -7333,6 +7339,9 @@ msgstr "" msgid "ClusterAgents|Agent never connected to GitLab" msgstr "" +msgid "ClusterAgents|All" +msgstr "" + msgid "ClusterAgents|Alternative installation methods" msgstr "" @@ -7351,6 +7360,9 @@ msgstr "" msgid "ClusterAgents|Configuration" msgstr "" +msgid "ClusterAgents|Connect existing cluster" +msgstr "" + msgid "ClusterAgents|Connect with Agent" msgstr "" @@ -7423,12 +7435,21 @@ msgstr "" msgid "ClusterAgents|Never connected" msgstr "" +msgid "ClusterAgents|No Agent based integrations" +msgstr "" + +msgid "ClusterAgents|No Certificate based integrations" +msgstr "" + msgid "ClusterAgents|Not connected" msgstr "" msgid "ClusterAgents|Read more about getting started" msgstr "" +msgid "ClusterAgents|Recommended" +msgstr "" + msgid "ClusterAgents|Recommended installation method" msgstr "" @@ -7477,6 +7498,12 @@ msgstr "" msgid "ClusterAgents|Use GitLab Agents to more securely integrate with your clusters to deploy your applications, run your pipelines, use review apps and much more." msgstr "" +msgid "ClusterAgents|View all %{number} Agent based integrations" +msgstr "" + +msgid "ClusterAgents|View all %{number} Certificate based integrations" +msgstr "" + msgid "ClusterAgents|You will need to create a token to connect to your agent" msgstr "" diff --git a/spec/features/projects/cluster_agents_spec.rb b/spec/features/projects/cluster_agents_spec.rb index 98ee6ca161f4df..3ef710169f0e59 100644 --- a/spec/features/projects/cluster_agents_spec.rb +++ b/spec/features/projects/cluster_agents_spec.rb @@ -22,7 +22,7 @@ end it 'displays empty state', :aggregate_failures do - expect(page).to have_content('Connect with a GitLab Agent') + expect(page).to have_content('Install new Agent') expect(page).to have_selector('.empty-state') end end diff --git a/spec/frontend/clusters_list/components/agents_spec.js b/spec/frontend/clusters_list/components/agents_spec.js index 3bd163c74eba83..238babdc2250a2 100644 --- a/spec/frontend/clusters_list/components/agents_spec.js +++ b/spec/frontend/clusters_list/components/agents_spec.js @@ -22,12 +22,12 @@ describe('Agents', () => { kasAddress: 'kas.example.com', }; - const createWrapper = ({ agents = [], pageInfo = null, trees = [] }) => { + const createWrapper = ({ agents = [], pageInfo = null, trees = [], count = 0 }) => { const provide = provideData; const apolloQueryResponse = { data: { project: { - clusterAgents: { nodes: agents, pageInfo, tokens: { nodes: [] } }, + clusterAgents: { nodes: agents, pageInfo, tokens: { nodes: [] }, count }, repository: { tree: { trees: { nodes: trees, pageInfo } } }, }, }, @@ -80,6 +80,8 @@ describe('Agents', () => { }, ]; + const count = 2; + const trees = [ { name: 'agent-2', @@ -120,7 +122,7 @@ describe('Agents', () => { ]; beforeEach(() => { - return createWrapper({ agents, trees }); + return createWrapper({ agents, count, trees }); }); it('should render agent table', () => { @@ -132,6 +134,10 @@ describe('Agents', () => { expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList); }); + it('should emit agents count to the parent component', () => { + expect(wrapper.emitted().onAgentsLoad).toEqual([[count]]); + }); + describe('when the agent has recently connected tokens', () => { it('should set agent status to active', () => { expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList); @@ -179,6 +185,21 @@ describe('Agents', () => { it('should pass pageInfo to the pagination component', () => { expect(findPaginationButtons().props()).toMatchObject(pageInfo); }); + + describe('when limit is passed from the parent component', () => { + beforeEach(() => { + propsData.limit = 6; + + return createWrapper({ + agents, + pageInfo, + }); + }); + + it('should not render pagination buttons', () => { + expect(findPaginationButtons().exists()).toBe(false); + }); + }); }); }); diff --git a/spec/frontend/clusters_list/components/clusters_empty_state_spec.js b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js index bda0e6c110c04a..fcee62e4dba556 100644 --- a/spec/frontend/clusters_list/components/clusters_empty_state_spec.js +++ b/spec/frontend/clusters_list/components/clusters_empty_state_spec.js @@ -11,6 +11,10 @@ const canAddCluster = true; describe('ClustersEmptyStateComponent', () => { let wrapper; + const propsData = { + childComponent: false, + }; + const provideData = { clustersEmptyStateImage, emptyStateHelpText: null, @@ -27,6 +31,7 @@ describe('ClustersEmptyStateComponent', () => { beforeEach(() => { wrapper = shallowMountExtended(ClustersEmptyState, { store: ClusterStore(entryData), + propsData, provide: provideData, stubs: { GlEmptyState }, }); @@ -36,8 +41,10 @@ describe('ClustersEmptyStateComponent', () => { wrapper.destroy(); }); - it('should render the action button', () => { - expect(findButton().exists()).toBe(true); + describe('when the component is loaded independently', () => { + it('should render the action button', () => { + expect(findButton().exists()).toBe(true); + }); }); describe('when the help text is not provided', () => { @@ -46,11 +53,31 @@ describe('ClustersEmptyStateComponent', () => { }); }); + describe('when the component is loaded as a child component', () => { + beforeEach(() => { + propsData.childComponent = true; + wrapper = shallowMountExtended(ClustersEmptyState, { + store: ClusterStore(entryData), + propsData, + provide: provideData, + }); + }); + + afterEach(() => { + propsData.childComponent = false; + }); + + it('should not render the action button', () => { + expect(findButton().exists()).toBe(false); + }); + }); + describe('when the help text is provided', () => { beforeEach(() => { provideData.emptyStateHelpText = emptyStateHelpText; wrapper = shallowMountExtended(ClustersEmptyState, { store: ClusterStore(entryData), + propsData, provide: provideData, }); }); @@ -61,8 +88,14 @@ describe('ClustersEmptyStateComponent', () => { }); describe('when the user cannot add clusters', () => { + entryData.canAddCluster = false; beforeEach(() => { - wrapper.vm.$store.state.canAddCluster = false; + wrapper = shallowMountExtended(ClustersEmptyState, { + store: ClusterStore(entryData), + propsData, + provide: provideData, + stubs: { GlEmptyState }, + }); }); it('should disable the button', () => { expect(findButton().props('disabled')).toBe(true); diff --git a/spec/frontend/clusters_list/components/clusters_main_view_spec.js b/spec/frontend/clusters_list/components/clusters_main_view_spec.js index 3869a2a87365d8..3905e9bb79ea4d 100644 --- a/spec/frontend/clusters_list/components/clusters_main_view_spec.js +++ b/spec/frontend/clusters_list/components/clusters_main_view_spec.js @@ -43,8 +43,9 @@ describe('ClustersMainViewComponent', () => { describe('tabs', () => { it.each` tabTitle | queryParamValue | lineNumber - ${'Agent'} | ${'agent'} | ${0} - ${'Certificate based'} | ${'certificate_based'} | ${1} + ${'All'} | ${'all'} | ${0} + ${'Agent'} | ${'agent'} | ${1} + ${'Certificate based'} | ${'certificate_based'} | ${2} `( 'renders correct tab title and query param value', ({ tabTitle, queryParamValue, lineNumber }) => { @@ -53,4 +54,13 @@ describe('ClustersMainViewComponent', () => { }, ); }); + + describe('when the child component emits the tab change event', () => { + beforeEach(() => { + findComponent().vm.$emit('changeTab', 'agent'); + }); + it('changes the tab', () => { + expect(wrapper.vm.$data.selectedTabIndex).toBe(1); + }); + }); }); diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js index de511788b089ff..8103bc8f35db18 100644 --- a/spec/frontend/clusters_list/components/clusters_spec.js +++ b/spec/frontend/clusters_list/components/clusters_spec.js @@ -105,6 +105,16 @@ describe('Clusters', () => { expect(findEmptyState().exists()).toBe(true); }); }); + + describe('when is loaded as a child component', () => { + beforeEach(() => { + wrapper.setProps({ limit: 6 }); + }); + + it("shouldn't render pagination buttons", () => { + expect(findPaginatedButtons().exists()).toBe(false); + }); + }); }); describe('cluster icon', () => { diff --git a/spec/frontend/clusters_list/components/clusters_view_all_spec.js b/spec/frontend/clusters_list/components/clusters_view_all_spec.js new file mode 100644 index 00000000000000..52802cae8d8bf7 --- /dev/null +++ b/spec/frontend/clusters_list/components/clusters_view_all_spec.js @@ -0,0 +1,218 @@ +import { GlCard, GlLoadingIcon, GlButton, GlSprintf } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ClustersViewAll from '~/clusters_list/components/clusters_view_all.vue'; +import Agents from '~/clusters_list/components/agents.vue'; +import Clusters from '~/clusters_list/components/clusters.vue'; +import ClusterStore from '~/clusters_list/store'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { + AGENT_CARD_INFO, + CERTIFICATE_BASED_CARD_INFO, + MAX_CLUSTERS_LIST, + INSTALL_AGENT_MODAL_ID, +} from '~/clusters_list/constants'; +import { sprintf } from '~/locale'; + +const loadingClusters = true; +const totalClusters = 0; +const addClusterPath = '/path/to/add/cluster'; +const defaultBranchName = 'default-branch'; + +describe('ClustersViewAllComponent', () => { + let wrapper; + let changeTabSpy; + + const event = { + preventDefault: jest.fn(), + }; + + const propsData = { + defaultBranchName, + }; + + const provideData = { + addClusterPath, + }; + + const entryData = { + loadingClusters, + totalClusters, + }; + + const findCards = () => wrapper.findAllComponents(GlCard); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findAgentsComponent = () => wrapper.findComponent(Agents); + const findClustersComponent = () => wrapper.findComponent(Clusters); + const findCardsContainer = () => wrapper.findByTestId('clusters-cards-container'); + const findAgentCardTitle = () => wrapper.findByTestId('agent-card-title'); + const findRecommendedBadge = () => wrapper.findByTestId('clusters-recommended-badge'); + const findClustersCardTitle = () => wrapper.findByTestId('clusters-card-title'); + const findFooterButton = (line) => findCards().at(line).findComponent(GlButton); + + beforeEach(() => { + wrapper = shallowMountExtended(ClustersViewAll, { + store: ClusterStore(entryData), + propsData, + provide: provideData, + directives: { + GlModalDirective: createMockDirective(), + }, + stubs: { GlCard, GlSprintf }, + }); + + changeTabSpy = jest.spyOn(wrapper.vm, 'changeTab'); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('should render 2 cards', () => { + expect(findCards().length).toBe(2); + }); + + describe('when agents and clusters are not loaded', () => { + it('should show the loading icon', () => { + expect(findLoadingIcon().exists()).toBe(true); + }); + }); + + describe('when both agents and clusters are loaded', () => { + beforeEach(() => { + wrapper.vm.$store.state.loadingClusters = false; + findAgentsComponent().vm.$emit('onAgentsLoad', 6); + }); + + it("shouldn't show the loading icon", () => { + expect(findLoadingIcon().exists()).toBe(false); + }); + + it('should make content visible', () => { + expect(findCardsContainer().classes('gl-display-none')).toBe(false); + }); + }); + + describe('agents card', () => { + it('should show recommended badge', () => { + expect(findRecommendedBadge().exists()).toBe(true); + }); + + it('should render Agents component', () => { + expect(findAgentsComponent().exists()).toBe(true); + }); + + it('should pass the limit prop', () => { + expect(findAgentsComponent().props('limit')).toBe(MAX_CLUSTERS_LIST); + }); + + it('should pass the default-branch-name prop', () => { + expect(findAgentsComponent().props('defaultBranchName')).toBe(defaultBranchName); + }); + + describe('when there are no agents', () => { + it('should show the empty title', () => { + expect(findAgentCardTitle().text()).toBe(AGENT_CARD_INFO.emptyTitle); + }); + + it('should show install new Agent button in the footer', () => { + expect(findFooterButton(0).exists()).toBe(true); + }); + + it('should render correct modal id for the agent link', () => { + const binding = getBinding(findFooterButton(0).element, 'gl-modal-directive'); + + expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID); + }); + }); + + describe('when the agents are present', () => { + const findFooterLink = () => wrapper.findByTestId('agents-tab-footer-link'); + const agentsNumber = 7; + beforeEach(() => { + findAgentsComponent().vm.$emit('onAgentsLoad', agentsNumber); + }); + + it('should show the correct title', () => { + expect(findAgentCardTitle().text()).toBe( + sprintf(AGENT_CARD_INFO.title, { number: MAX_CLUSTERS_LIST, total: agentsNumber }), + ); + }); + + it('should show the link to the Agents tab in the footer', () => { + expect(findFooterLink().exists()).toBe(true); + expect(findFooterLink().text()).toBe( + sprintf(AGENT_CARD_INFO.footerText, { number: agentsNumber }), + ); + expect(findFooterLink().attributes('href')).toBe('?tab=agent'); + }); + + describe('when clicking on the footer link', () => { + beforeEach(() => { + findFooterLink().vm.$emit('click', event); + }); + + it('should trigger tab change', () => { + expect(changeTabSpy).toHaveBeenCalledWith(event, 'agent'); + }); + }); + }); + }); + + describe('clusters tab', () => { + it('should pass the limit prop', () => { + expect(findClustersComponent().props('limit')).toBe(MAX_CLUSTERS_LIST); + }); + + it('should pass the child-component prop', () => { + expect(findClustersComponent().props('childComponent')).toBe(true); + }); + + describe('when there are no clusters', () => { + it('should show the empty title', () => { + expect(findClustersCardTitle().text()).toBe(CERTIFICATE_BASED_CARD_INFO.emptyTitle); + }); + + it('should show install new Agent button in the footer', () => { + expect(findFooterButton(1).exists()).toBe(true); + }); + + it('should render correct href for the button in the footer', () => { + expect(findFooterButton(1).attributes('href')).toBe(addClusterPath); + }); + }); + + describe('when the clusters are present', () => { + const findFooterLink = () => wrapper.findByTestId('clusters-tab-footer-link'); + const clustersNumber = 7; + beforeEach(() => { + wrapper.vm.$store.state.totalClusters = clustersNumber; + }); + + it('should show the correct title', () => { + expect(findClustersCardTitle().text()).toBe( + sprintf(CERTIFICATE_BASED_CARD_INFO.title, { + number: MAX_CLUSTERS_LIST, + total: clustersNumber, + }), + ); + }); + + it('should show the link to the Clusters tab in the footer', () => { + expect(findFooterLink().exists()).toBe(true); + expect(findFooterLink().text()).toBe( + sprintf(CERTIFICATE_BASED_CARD_INFO.footerText, { number: clustersNumber }), + ); + }); + + describe('when clicking on the footer link', () => { + beforeEach(() => { + findFooterLink().vm.$emit('click', event); + }); + + it('should trigger tab change', () => { + expect(changeTabSpy).toHaveBeenCalledWith(event, 'certificate_based'); + }); + }); + }); + }); +}); diff --git a/spec/frontend/clusters_list/store/mutations_spec.js b/spec/frontend/clusters_list/store/mutations_spec.js index f8723bfcdfc0af..ae264eee4496cd 100644 --- a/spec/frontend/clusters_list/store/mutations_spec.js +++ b/spec/frontend/clusters_list/store/mutations_spec.js @@ -57,4 +57,12 @@ describe('Admin statistics panel mutations', () => { expect(state.page).toBe(123); }); }); + + describe(`${types.SET_CLUSTERS_PER_PAGE}`, () => { + it('changes clustersPerPage value', () => { + mutations[types.SET_CLUSTERS_PER_PAGE](state, 123); + + expect(state.clustersPerPage).toBe(123); + }); + }); }); -- GitLab From 32f5f7be3f401033be70f59ae2b8793aea53b033 Mon Sep 17 00:00:00 2001 From: anna_vovchenko Date: Thu, 11 Nov 2021 11:58:19 +0200 Subject: [PATCH 08/12] Implemented changes after the FE review --- .../components/clusters_view_all.vue | 7 +-- .../components/install_agent_modal.vue | 52 ------------------- .../clusters_list/components/agents_spec.js | 14 ++--- .../components/clusters_main_view_spec.js | 2 +- .../clusters_list/components/clusters_spec.js | 10 ++-- .../components/clusters_view_all_spec.js | 2 +- 6 files changed, 16 insertions(+), 71 deletions(-) diff --git a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue index a95d07fea85e23..40a0fa58fbf99c 100644 --- a/app/assets/javascripts/clusters_list/components/clusters_view_all.vue +++ b/app/assets/javascripts/clusters_list/components/clusters_view_all.vue @@ -100,12 +100,7 @@ export default {