Commit b83ebdd4 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '340759-implementation-revisit-the-kubernetes-section-ux-6' into 'master'

Add view all tab to the clusters page

See merge request gitlab-org/gitlab!74135
parents 972c82a1 d2d03188
......@@ -11,9 +11,6 @@ export default {
getStartedDocsUrl: helpPagePath('user/clusters/agent/index', {
anchor: 'define-a-configuration-repository',
}),
integrationsDocsUrl: helpPagePath('user/clusters/agent/index', {
anchor: 'get-started-with-gitops-and-the-gitlab-agent',
}),
components: {
GlButton,
GlEmptyState,
......@@ -30,6 +27,11 @@ export default {
type: Boolean,
required: true,
},
isChildComponent: {
default: false,
required: false,
type: Boolean,
},
},
computed: {
repositoryPath() {
......@@ -92,6 +94,7 @@ export default {
<template #actions>
<gl-button
v-if="!isChildComponent"
v-gl-modal-directive="$options.modalId"
:disabled="!hasConfigurations"
data-testid="integration-primary-button"
......
......@@ -20,6 +20,9 @@ export default {
this.updateTreeList(data);
return data;
},
result() {
this.emitAgentsLoaded();
},
},
},
components: {
......@@ -36,11 +39,21 @@ export default {
required: false,
type: String,
},
isChildComponent: {
default: false,
required: false,
type: Boolean,
},
limit: {
default: null,
required: false,
type: Number,
},
},
data() {
return {
cursor: {
first: MAX_LIST_COUNT,
first: this.limit ? this.limit : MAX_LIST_COUNT,
last: null,
},
folderList: {},
......@@ -68,7 +81,7 @@ export default {
return this.$apollo.queries.agents.loading;
},
showPagination() {
return this.agentPageInfo.hasPreviousPage || this.agentPageInfo.hasNextPage;
return !this.limit && (this.agentPageInfo.hasPreviousPage || this.agentPageInfo.hasNextPage);
},
treePageInfo() {
return this.agents?.project?.repository?.tree?.trees?.pageInfo || {};
......@@ -128,6 +141,10 @@ export default {
}
return 'unused';
},
emitAgentsLoaded() {
const count = this.agents?.project?.clusterAgents?.count;
this.$emit('onAgentsLoad', count);
},
},
};
</script>
......@@ -144,7 +161,11 @@ export default {
</div>
</div>
<agent-empty-state v-else :has-configurations="hasConfigurations" />
<agent-empty-state
v-else
:has-configurations="hasConfigurations"
:is-child-component="isChildComponent"
/>
</section>
<gl-alert v-else variant="danger" :dismissible="false">
......
......@@ -34,6 +34,18 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
isChildComponent: {
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 {
</template>
</gl-table>
<clusters-empty-state v-else />
<clusters-empty-state v-else :is-child-component="isChildComponent" />
<gl-pagination
v-if="hasClustersPerPage"
v-if="hasClustersPerPage && !limit"
v-model="currentPage"
:per-page="clustersPerPage"
:total-items="totalClusters"
......
......@@ -13,6 +13,13 @@ export default {
GlSprintf,
},
inject: ['emptyStateHelpText', 'clustersEmptyStateImage', 'newClusterPath'],
props: {
isChildComponent: {
default: false,
required: false,
type: Boolean,
},
},
learnMoreHelpUrl: helpPagePath('user/project/clusters/index'),
multipleClustersHelpUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
computed: {
......@@ -54,6 +61,7 @@ export default {
<template #actions>
<gl-button
v-if="!isChildComponent"
data-testid="integration-primary-button"
data-qa-selector="add_kubernetes_cluster_link"
category="primary"
......
<script>
import { GlTabs, GlTab } from '@gitlab/ui';
import { CLUSTERS_TABS } from '../constants';
import { CLUSTERS_TABS, MAX_CLUSTERS_LIST, MAX_LIST_COUNT, AGENT } from '../constants';
import Agents from './agents.vue';
import InstallAgentModal from './install_agent_modal.vue';
import ClustersActions from './clusters_actions.vue';
import Clusters from './clusters.vue';
import ClustersViewAll from './clusters_view_all.vue';
export default {
components: {
GlTabs,
GlTab,
ClustersActions,
ClustersViewAll,
Clusters,
Agents,
InstallAgentModal,
......@@ -26,11 +28,14 @@ export default {
data() {
return {
selectedTabIndex: 0,
maxAgents: MAX_CLUSTERS_LIST,
};
},
methods: {
onTabChange(tabName) {
this.selectedTabIndex = CLUSTERS_TABS.findIndex((tab) => tab.queryParamValue === tabName);
this.maxAgents = tabName === AGENT ? MAX_LIST_COUNT : MAX_CLUSTERS_LIST;
},
},
};
......@@ -63,6 +68,6 @@ export default {
</template>
</gl-tabs>
<install-agent-modal :default-branch-name="defaultBranchName" />
<install-agent-modal :default-branch-name="defaultBranchName" :max-agents="maxAgents" />
</div>
</template>
<script>
import {
GlCard,
GlSprintf,
GlPopover,
GlLink,
GlButton,
GlBadge,
GlLoadingIcon,
GlModalDirective,
} from '@gitlab/ui';
import { mapState } from 'vuex';
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,
GlBadge,
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;
},
agentsCardTitle() {
let cardTitle;
if (this.totalAgents > 0) {
cardTitle = {
message: AGENT_CARD_INFO.title,
number: this.totalAgents < MAX_CLUSTERS_LIST ? this.totalAgents : MAX_CLUSTERS_LIST,
total: this.totalAgents,
};
} else {
cardTitle = {
message: AGENT_CARD_INFO.emptyTitle,
};
}
return cardTitle;
},
clustersCardTitle() {
let cardTitle;
if (this.totalClusters > 0) {
cardTitle = {
message: CERTIFICATE_BASED_CARD_INFO.title,
number: this.totalClusters < MAX_CLUSTERS_LIST ? this.totalClusters : MAX_CLUSTERS_LIST,
total: this.totalClusters,
};
} else {
cardTitle = {
message: CERTIFICATE_BASED_CARD_INFO.emptyTitle,
};
}
return cardTitle;
},
},
methods: {
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);
},
},
};
</script>
<template>
<div>
<gl-loading-icon v-if="isLoading" size="md" />
<div v-show="!isLoading" data-testid="clusters-cards-container">
<gl-card
header-class="gl-bg-white gl-display-flex gl-align-items-center gl-justify-content-space-between gl-py-4"
body-class="gl-pb-0"
footer-class="gl-text-right"
>
<template #header>
<h3 data-testid="agent-card-title" class="gl-my-0 gl-font-weight-normal gl-font-size-h2">
<gl-sprintf :message="agentsCardTitle.message"
><template #number>{{ agentsCardTitle.number }}</template>
<template #total>{{ agentsCardTitle.total }}</template>
</gl-sprintf>
</h3>
<gl-badge id="clusters-recommended-badge" size="md" variant="info">{{
$options.AGENT_CARD_INFO.tooltip.label
}}</gl-badge>
<gl-popover
target="clusters-recommended-badge"
container="viewport"
placement="bottom"
:title="$options.AGENT_CARD_INFO.tooltip.title"
>
<p class="gl-mb-0">
<gl-sprintf :message="$options.AGENT_CARD_INFO.tooltip.text">
<template #link="{ content }">
<gl-link
:href="$options.AGENT_CARD_INFO.tooltip.link"
target="_blank"
class="gl-font-sm"
>
{{ content }}</gl-link
>
</template>
</gl-sprintf>
</p>
</gl-popover>
</template>
<agents
:limit="$options.MAX_CLUSTERS_LIST"
:default-branch-name="defaultBranchName"
:is-child-component="true"
@onAgentsLoad="onAgentsLoad"
/>
<template #footer>
<gl-link
v-if="totalAgents"
data-testid="agents-tab-footer-link"
:href="`?tab=${$options.AGENT_CARD_INFO.tabName}`"
@click="changeTab($event, $options.AGENT_CARD_INFO.tabName)"
><gl-sprintf :message="$options.AGENT_CARD_INFO.footerText"
><template #number>{{ cardFooterNumber(totalAgents) }}</template></gl-sprintf
></gl-link
><gl-button
v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
class="gl-ml-4"
category="secondary"
variant="confirm"
>{{ $options.AGENT_CARD_INFO.actionText }}</gl-button
>
</template>
</gl-card>
<gl-card
class="gl-mt-6"
header-class="gl-bg-white gl-display-flex gl-align-items-center gl-justify-content-space-between"
body-class="gl-pb-0"
footer-class="gl-text-right"
>
<template #header>
<h3
class="gl-my-1 gl-font-weight-normal gl-font-size-h2"
data-testid="clusters-card-title"
>
<gl-sprintf :message="clustersCardTitle.message"
><template #number>{{ clustersCardTitle.number }}</template>
<template #total>{{ clustersCardTitle.total }}</template>
</gl-sprintf>
</h3>
</template>
<clusters :limit="$options.MAX_CLUSTERS_LIST" :is-child-component="true" />
<template #footer>
<gl-link
v-if="totalClusters"
data-testid="clusters-tab-footer-link"
:href="`?tab=${$options.CERTIFICATE_BASED_CARD_INFO.tabName}`"
@click="changeTab($event, $options.CERTIFICATE_BASED_CARD_INFO.tabName)"
><gl-sprintf :message="$options.CERTIFICATE_BASED_CARD_INFO.footerText"
><template #number>{{ cardFooterNumber(totalClusters) }}</template></gl-sprintf
></gl-link
><gl-button
category="secondary"
variant="confirm"
class="gl-ml-4"
:href="addClusterPath"
>{{ $options.CERTIFICATE_BASED_CARD_INFO.actionText }}</gl-button
>
</template>
</gl-card>
</div>
</div>
</template>
......@@ -12,7 +12,7 @@ import { helpPagePath } from '~/helpers/help_page_helper';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue';
import { generateAgentRegistrationCommand } from '../clusters_util';
import { INSTALL_AGENT_MODAL_ID, I18N_INSTALL_AGENT_MODAL, MAX_LIST_COUNT } from '../constants';
import { INSTALL_AGENT_MODAL_ID, I18N_INSTALL_AGENT_MODAL } from '../constants';
import { addAgentToStore } from '../graphql/cache_update';
import createAgent from '../graphql/mutations/create_agent.mutation.graphql';
import createAgentToken from '../graphql/mutations/create_agent_token.mutation.graphql';
......@@ -41,6 +41,10 @@ export default {
required: false,
type: String,
},
maxAgents: {
required: true,
type: Number,
},
},
data() {
return {
......@@ -75,7 +79,7 @@ export default {
getAgentsQueryVariables() {
return {
defaultBranchName: this.defaultBranchName,
first: MAX_LIST_COUNT,
first: this.maxAgents,
last: null,
projectPath: this.projectPath,
};
......
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',
......@@ -186,3 +220,6 @@ export const CLUSTERS_ACTIONS = {
connectWithAgent: s__('ClusterAgents|Connect with Agent'),
connectExistingCluster: s__('ClusterAgents|Connect with certificate'),
};
export const AGENT = 'agent';
export const CERTIFICATE_BASED = 'certificate_based';
......@@ -17,6 +17,7 @@ export function addAgentToStore(store, createClusterAgent, query, variables) {
};
draftData.project.clusterAgents.nodes.push(clusterAgent);
draftData.project.clusterAgents.count += 1;
draftData.project.repository.tree.trees.nodes.push(configuration);
});
......
......@@ -20,6 +20,8 @@ query getAgents(
pageInfo {
...PageInfo
}
count
}
repository {
......
......@@ -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);
};
......@@ -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';
......@@ -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;
},
};
......@@ -5,7 +5,7 @@ export default (initialState = {}) => ({
endpoint: initialState.endpoint,
hasAncestorClusters: false,
clusters: [],
clustersPerPage: 0,
clustersPerPage: 20,
loadingClusters: true,
loadingNodes: true,
page: 1,
......
......@@ -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 ""
......@@ -7387,6 +7399,12 @@ msgstr ""
msgid "ClusterAgents|For alternative installation methods %{linkStart}go to the documentation%{linkEnd}."
msgstr ""
msgid "ClusterAgents|GitLab Agents"
msgstr ""
msgid "ClusterAgents|GitLab Agents provide an increased level of security when integrating with clusters. %{linkStart}Learn more about the GitLab Kubernetes Agent.%{linkEnd}"
msgstr ""
msgid "ClusterAgents|Go to the repository"
msgstr ""
......@@ -7423,12 +7441,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 +7504,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 ""
......
......@@ -22,7 +22,7 @@ RSpec.describe 'ClusterAgents', :js do
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
......
......@@ -14,7 +14,7 @@ localVue.use(VueApollo);
describe('Agents', () => {
let wrapper;
const propsData = {
const defaultProps = {
defaultBranchName: 'default',
};
const provideData = {
......@@ -22,12 +22,12 @@ describe('Agents', () => {
kasAddress: 'kas.example.com',
};
const createWrapper = ({ agents = [], pageInfo = null, trees = [] }) => {
const createWrapper = ({ props = {}, 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 } } },
},
},
......@@ -40,7 +40,10 @@ describe('Agents', () => {
wrapper = shallowMount(Agents, {
localVue,
apolloProvider,
propsData,
propsData: {
...defaultProps,
...props,
},
provide: provideData,
});
......@@ -80,6 +83,8 @@ describe('Agents', () => {
},
];
const count = 2;
const trees = [
{
name: 'agent-2',
......@@ -120,7 +125,7 @@ describe('Agents', () => {
];
beforeEach(() => {
return createWrapper({ agents, trees });
return createWrapper({ agents, count, trees });
});
it('should render agent table', () => {
......@@ -132,6 +137,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 +188,20 @@ 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(() => {
return createWrapper({
props: { limit: 6 },
agents,
pageInfo,
});
});
it('should not render pagination buttons', () => {
expect(findPaginationButtons().exists()).toBe(false);
});
});
});
});
......@@ -235,7 +258,7 @@ describe('Agents', () => {
beforeEach(() => {
wrapper = shallowMount(Agents, {
mocks,
propsData,
propsData: defaultProps,
provide: provideData,
});
......
......@@ -11,6 +11,10 @@ const canAddCluster = true;
describe('ClustersEmptyStateComponent', () => {
let wrapper;
const propsData = {
isChildComponent: 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,9 +41,11 @@ describe('ClustersEmptyStateComponent', () => {
wrapper.destroy();
});
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', () => {
it('should not render the empty state text', () => {
......@@ -46,11 +53,31 @@ describe('ClustersEmptyStateComponent', () => {
});
});
describe('when the component is loaded as a child component', () => {
beforeEach(() => {
propsData.isChildComponent = true;
wrapper = shallowMountExtended(ClustersEmptyState, {
store: ClusterStore(entryData),
propsData,
provide: provideData,
});
});
afterEach(() => {
propsData.isChildComponent = 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);
......
import { GlTabs, GlTab } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ClustersMainView from '~/clusters_list/components/clusters_main_view.vue';
import { CLUSTERS_TABS } from '~/clusters_list/constants';
import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue';
import {
AGENT,
CERTIFICATE_BASED,
CLUSTERS_TABS,
MAX_CLUSTERS_LIST,
MAX_LIST_COUNT,
} from '~/clusters_list/constants';
const defaultBranchName = 'default-branch';
......@@ -26,6 +33,7 @@ describe('ClustersMainViewComponent', () => {
const findAllTabs = () => wrapper.findAllComponents(GlTab);
const findGlTabAtIndex = (index) => findAllTabs().at(index);
const findComponent = () => wrapper.findByTestId('clusters-tab-component');
const findModal = () => wrapper.findComponent(InstallAgentModal);
it('renders `GlTabs` with `syncActiveTabWithQueryParams` and `queryParamName` props set', () => {
expect(findTabs().exists()).toBe(true);
......@@ -40,11 +48,16 @@ describe('ClustersMainViewComponent', () => {
expect(findComponent().props('defaultBranchName')).toBe(defaultBranchName);
});
it('passes correct max-agents param to the modal', () => {
expect(findModal().props('maxAgents')).toBe(MAX_CLUSTERS_LIST);
});
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 +66,17 @@ describe('ClustersMainViewComponent', () => {
},
);
});
describe('when the child component emits the tab change event', () => {
beforeEach(() => {
findComponent().vm.$emit('changeTab', AGENT);
});
it('changes the tab', () => {
expect(findTabs().attributes('value')).toBe('1');
});
it('passes correct max-agents param to the modal', () => {
expect(findModal().props('maxAgents')).toBe(MAX_LIST_COUNT);
});
});
});
......@@ -48,9 +48,9 @@ describe('Clusters', () => {
mock.onGet(`${endpoint}?page=${header['x-page']}`).reply(response, body, header);
};
const mountWrapper = () => {
const createWrapper = ({ propsData = {} }) => {
store = ClusterStore(entryData);
wrapper = mount(Clusters, { provide: provideData, store, stubs: { GlTable } });
wrapper = mount(Clusters, { propsData, provide: provideData, store, stubs: { GlTable } });
return axios.waitForAll();
};
......@@ -70,7 +70,7 @@ describe('Clusters', () => {
mock = new MockAdapter(axios);
mockPollingApi(200, apiData, paginationHeader());
return mountWrapper();
return createWrapper({});
});
afterEach(() => {
......@@ -105,6 +105,16 @@ describe('Clusters', () => {
expect(findEmptyState().exists()).toBe(true);
});
});
describe('when is loaded as a child component', () => {
beforeEach(() => {
createWrapper({ limit: 6 });
});
it("shouldn't render pagination buttons", () => {
expect(findPaginatedButtons().exists()).toBe(false);
});
});
});
describe('cluster icon', () => {
......@@ -248,7 +258,7 @@ describe('Clusters', () => {
beforeEach(() => {
mockPollingApi(200, apiData, paginationHeader(totalFirstPage, perPage, 1));
return mountWrapper();
return createWrapper({});
});
it('should load to page 1 with header values', () => {
......
import { GlCard, GlLoadingIcon, GlButton, GlSprintf, GlBadge } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
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 { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import {
AGENT,
CERTIFICATE_BASED,
AGENT_CARD_INFO,
CERTIFICATE_BASED_CARD_INFO,
MAX_CLUSTERS_LIST,
INSTALL_AGENT_MODAL_ID,
} from '~/clusters_list/constants';
import { sprintf } from '~/locale';
const localVue = createLocalVue();
localVue.use(Vuex);
const addClusterPath = '/path/to/add/cluster';
const defaultBranchName = 'default-branch';
describe('ClustersViewAllComponent', () => {
let wrapper;
const event = {
preventDefault: jest.fn(),
};
const propsData = {
defaultBranchName,
};
const provideData = {
addClusterPath,
};
const entryData = {
loadingClusters: false,
totalClusters: 0,
};
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.findComponent(GlBadge);
const findClustersCardTitle = () => wrapper.findByTestId('clusters-card-title');
const findFooterButton = (line) => findCards().at(line).findComponent(GlButton);
const createStore = (initialState) =>
new Vuex.Store({
state: initialState,
});
const createWrapper = ({ initialState }) => {
wrapper = shallowMountExtended(ClustersViewAll, {
localVue,
store: createStore(initialState),
propsData,
provide: provideData,
directives: {
GlModalDirective: createMockDirective(),
},
stubs: { GlCard, GlSprintf },
});
};
beforeEach(() => {
createWrapper({ initialState: entryData });
});
afterEach(() => {
wrapper.destroy();
});
describe('when agents and clusters are not loaded', () => {
const initialState = {
loadingClusters: true,
totalClusters: 0,
};
beforeEach(() => {
createWrapper({ initialState });
});
it('should show the loading icon', () => {
expect(findLoadingIcon().exists()).toBe(true);
});
});
describe('when both agents and clusters are loaded', () => {
beforeEach(() => {
findAgentsComponent().vm.$emit('onAgentsLoad', 6);
});
it("shouldn't show the loading icon", () => {
expect(findLoadingIcon().exists()).toBe(false);
});
it('should make content visible', () => {
expect(findCardsContainer().isVisible()).toBe(true);
});
it('should render 2 cards', () => {
expect(findCards().length).toBe(2);
});
});
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(wrapper.emitted('changeTab')).toEqual([[AGENT]]);
});
});
});
});
describe('clusters tab', () => {
it('should pass the limit prop', () => {
expect(findClustersComponent().props('limit')).toBe(MAX_CLUSTERS_LIST);
});
it('should pass the is-child-component prop', () => {
expect(findClustersComponent().props('isChildComponent')).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;
const initialState = {
loadingClusters: false,
totalClusters: clustersNumber,
};
beforeEach(() => {
createWrapper({ initialState });
});
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(wrapper.emitted('changeTab')).toEqual([[CERTIFICATE_BASED]]);
});
});
});
});
});
......@@ -24,6 +24,7 @@ localVue.use(VueApollo);
const projectPath = 'path/to/project';
const defaultBranchName = 'default';
const maxAgents = MAX_LIST_COUNT;
describe('InstallAgentModal', () => {
let wrapper;
......@@ -56,6 +57,7 @@ describe('InstallAgentModal', () => {
const propsData = {
defaultBranchName,
maxAgents,
};
wrapper = shallowMount(InstallAgentModal, {
......
......@@ -16,6 +16,7 @@ const pageInfo = {
hasPreviousPage: false,
startCursor: '',
};
const count = 1;
export const createAgentResponse = {
data: {
......@@ -64,7 +65,7 @@ export const createAgentTokenErrorResponse = {
export const getAgentResponse = {
data: {
project: {
clusterAgents: { nodes: [{ ...agent, tokens }], pageInfo },
clusterAgents: { nodes: [{ ...agent, tokens }], pageInfo, count },
repository: {
tree: {
trees: { nodes: [{ ...agent, path: null }], pageInfo },
......
......@@ -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);
});
});
});
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