Commit c9563929 authored by Phil Hughes's avatar Phil Hughes

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

Develop two variants of the Install new Agent modal

See merge request gitlab-org/gitlab!73636
parents 43d76937 4f3f89bf
<script> <script>
import { GlButton, GlEmptyState, GlLink, GlSprintf, GlAlert, GlModalDirective } from '@gitlab/ui'; import { GlButton, GlEmptyState, GlLink, GlSprintf, GlModalDirective } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper'; import { helpPagePath } from '~/helpers/help_page_helper';
import { INSTALL_AGENT_MODAL_ID, I18N_AGENTS_EMPTY_STATE } from '../constants'; import { INSTALL_AGENT_MODAL_ID, I18N_AGENTS_EMPTY_STATE } from '../constants';
...@@ -8,46 +8,33 @@ export default { ...@@ -8,46 +8,33 @@ export default {
modalId: INSTALL_AGENT_MODAL_ID, modalId: INSTALL_AGENT_MODAL_ID,
multipleClustersDocsUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'), multipleClustersDocsUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
installDocsUrl: helpPagePath('administration/clusters/kas'), installDocsUrl: helpPagePath('administration/clusters/kas'),
getStartedDocsUrl: helpPagePath('user/clusters/agent/index', {
anchor: 'define-a-configuration-repository',
}),
components: { components: {
GlButton, GlButton,
GlEmptyState, GlEmptyState,
GlLink, GlLink,
GlSprintf, GlSprintf,
GlAlert,
}, },
directives: { directives: {
GlModalDirective, GlModalDirective,
}, },
inject: ['emptyStateImage', 'projectPath'], inject: ['emptyStateImage'],
props: { props: {
hasConfigurations: {
type: Boolean,
required: true,
},
isChildComponent: { isChildComponent: {
default: false, default: false,
required: false, required: false,
type: Boolean, type: Boolean,
}, },
}, },
computed: {
repositoryPath() {
return `/${this.projectPath}`;
},
},
}; };
</script> </script>
<template> <template>
<gl-empty-state :svg-path="emptyStateImage" title="" class="agents-empty-state"> <gl-empty-state :svg-path="emptyStateImage" title="" class="agents-empty-state">
<template #description> <template #description>
<p class="mw-460 gl-mx-auto gl-text-left"> <p class="gl-text-left">
{{ $options.i18n.introText }} {{ $options.i18n.introText }}
</p> </p>
<p class="mw-460 gl-mx-auto gl-text-left"> <p class="gl-text-left">
<gl-sprintf :message="$options.i18n.multipleClustersText"> <gl-sprintf :message="$options.i18n.multipleClustersText">
<template #link="{ content }"> <template #link="{ content }">
<gl-link <gl-link
...@@ -61,42 +48,17 @@ export default { ...@@ -61,42 +48,17 @@ export default {
</gl-sprintf> </gl-sprintf>
</p> </p>
<p class="mw-460 gl-mx-auto"> <p>
<gl-link :href="$options.installDocsUrl" target="_blank" data-testid="install-docs-link"> <gl-link :href="$options.installDocsUrl" target="_blank" data-testid="install-docs-link">
{{ $options.i18n.learnMoreText }} {{ $options.i18n.learnMoreText }}
</gl-link> </gl-link>
</p> </p>
<gl-alert
v-if="!hasConfigurations"
variant="warning"
class="gl-mb-5 text-left"
:dismissible="false"
>
{{ $options.i18n.warningText }}
<template #actions>
<gl-button
category="primary"
variant="info"
:href="$options.getStartedDocsUrl"
target="_blank"
class="gl-ml-0!"
>
{{ $options.i18n.readMoreText }}
</gl-button>
<gl-button category="secondary" variant="info" :href="repositoryPath">
{{ $options.i18n.repositoryButtonText }}
</gl-button>
</template>
</gl-alert>
</template> </template>
<template #actions> <template #actions>
<gl-button <gl-button
v-if="!isChildComponent" v-if="!isChildComponent"
v-gl-modal-directive="$options.modalId" v-gl-modal-directive="$options.modalId"
:disabled="!hasConfigurations"
data-testid="integration-primary-button" data-testid="integration-primary-button"
category="primary" category="primary"
variant="confirm" variant="confirm"
......
...@@ -86,9 +86,6 @@ export default { ...@@ -86,9 +86,6 @@ export default {
treePageInfo() { treePageInfo() {
return this.agents?.project?.repository?.tree?.trees?.pageInfo || {}; return this.agents?.project?.repository?.tree?.trees?.pageInfo || {};
}, },
hasConfigurations() {
return Boolean(this.agents?.project?.repository?.tree?.trees?.nodes?.length);
},
}, },
methods: { methods: {
reloadAgents() { reloadAgents() {
...@@ -161,11 +158,7 @@ export default { ...@@ -161,11 +158,7 @@ export default {
</div> </div>
</div> </div>
<agent-empty-state <agent-empty-state v-else :is-child-component="isChildComponent" />
v-else
:has-configurations="hasConfigurations"
:is-child-component="isChildComponent"
/>
</section> </section>
<gl-alert v-else variant="danger" :dismissible="false"> <gl-alert v-else variant="danger" :dismissible="false">
......
<script> <script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '../constants'; import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '../constants';
import agentConfigurations from '../graphql/queries/agent_configurations.query.graphql';
export default { export default {
name: 'AvailableAgentsDropdown', name: 'AvailableAgentsDropdown',
...@@ -10,36 +9,22 @@ export default { ...@@ -10,36 +9,22 @@ export default {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
}, },
inject: ['projectPath'],
props: { props: {
isRegistering: { isRegistering: {
required: true, required: true,
type: Boolean, type: Boolean,
}, },
}, availableAgents: {
apollo: { required: true,
agents: { type: Array,
query: agentConfigurations,
variables() {
return {
projectPath: this.projectPath,
};
},
update(data) {
this.populateAvailableAgents(data);
},
}, },
}, },
data() { data() {
return { return {
availableAgents: [],
selectedAgent: null, selectedAgent: null,
}; };
}, },
computed: { computed: {
isLoading() {
return this.$apollo.queries.agents.loading;
},
dropdownText() { dropdownText() {
if (this.isRegistering) { if (this.isRegistering) {
return this.$options.i18n.registeringAgent; return this.$options.i18n.registeringAgent;
...@@ -58,18 +43,11 @@ export default { ...@@ -58,18 +43,11 @@ export default {
isSelected(agent) { isSelected(agent) {
return this.selectedAgent === agent; return this.selectedAgent === agent;
}, },
populateAvailableAgents(data) {
const installedAgents = data?.project?.clusterAgents?.nodes.map((agent) => agent.name) ?? [];
const configuredAgents =
data?.project?.agentConfigurations?.nodes.map((config) => config.agentName) ?? [];
this.availableAgents = configuredAgents.filter((agent) => !installedAgents.includes(agent));
},
}, },
}; };
</script> </script>
<template> <template>
<gl-dropdown :text="dropdownText" :loading="isLoading || isRegistering"> <gl-dropdown :text="dropdownText" :loading="isRegistering">
<gl-dropdown-item <gl-dropdown-item
v-for="agent in availableAgents" v-for="agent in availableAgents"
:key="agent" :key="agent"
......
...@@ -64,17 +64,19 @@ export const STATUSES = { ...@@ -64,17 +64,19 @@ export const STATUSES = {
creating: { title: __('Creating') }, creating: { title: __('Creating') },
}; };
export const I18N_INSTALL_AGENT_MODAL = { export const I18N_AGENT_MODAL = {
register: {
registerAgentButton: s__('ClusterAgents|Register Agent'), registerAgentButton: s__('ClusterAgents|Register Agent'),
close: __('Close'), close: __('Close'),
cancel: __('Cancel'), cancel: __('Cancel'),
modalTitle: s__('ClusterAgents|Install new Agent'), modalTitle: s__('ClusterAgents|Connect with Agent'),
selectAgentTitle: s__('ClusterAgents|Select which Agent you want to install'), selectAgentTitle: s__('ClusterAgents|Select which Agent you want to install'),
selectAgentBody: s__( selectAgentBody: s__(
`ClusterAgents|Select the Agent you want to register with GitLab and install on your cluster. To learn more about the Kubernetes Agent registration process %{linkStart}go to the documentation%{linkEnd}.`, 'ClusterAgents|Select an Agent to register with GitLab and install on your cluster.',
), ),
learnMoreLink: s__('ClusterAgents|Learn more about the GitLab Kubernetes Agent registration.'),
copyToken: s__('ClusterAgents|Copy token'), copyToken: s__('ClusterAgents|Copy token'),
tokenTitle: s__('ClusterAgents|Registration token'), tokenTitle: s__('ClusterAgents|Registration token'),
...@@ -101,8 +103,24 @@ export const I18N_INSTALL_AGENT_MODAL = { ...@@ -101,8 +103,24 @@ export const I18N_INSTALL_AGENT_MODAL = {
registrationErrorTitle: __('Failed to register Agent'), registrationErrorTitle: __('Failed to register Agent'),
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'), unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
},
install: {
modalTitle: s__('ClusterAgents|Install new Agent'),
modalBody: s__(
'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.',
),
docsLinkText: s__('ClusterAgents|Learn more about installing a GitLab Kubernetes Agent'),
enableKasText: s__(
'ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}',
),
altText: s__('ClusterAgents|GitLab Kubernetes Agent'),
secondaryButton: s__('ClusterAgents|Go to the repository'),
done: __('Done'),
},
}; };
export const KAS_DISABLED_ERROR = 'Gitlab::Kas::Client::ConfigurationError';
export const I18N_AVAILABLE_AGENTS_DROPDOWN = { export const I18N_AVAILABLE_AGENTS_DROPDOWN = {
selectAgent: s__('ClusterAgents|Select an Agent'), selectAgent: s__('ClusterAgents|Select an Agent'),
registeringAgent: s__('ClusterAgents|Registering Agent'), registeringAgent: s__('ClusterAgents|Registering Agent'),
...@@ -149,11 +167,6 @@ export const I18N_AGENTS_EMPTY_STATE = { ...@@ -149,11 +167,6 @@ export const I18N_AGENTS_EMPTY_STATE = {
'ClusterAgents|If you are setting up multiple clusters and are using Auto DevOps, %{linkStart}read about using multiple Kubernetes clusters first.%{linkEnd}', 'ClusterAgents|If you are setting up multiple clusters and are using Auto DevOps, %{linkStart}read about using multiple Kubernetes clusters first.%{linkEnd}',
), ),
learnMoreText: s__('ClusterAgents|Learn more about the GitLab Kubernetes Agent.'), learnMoreText: s__('ClusterAgents|Learn more about the GitLab Kubernetes Agent.'),
warningText: s__(
'ClusterAgents|To install an Agent you should create an agent directory in the Repository first. We recommend that you add the Agent configuration to the directory before you start the installation process.',
),
readMoreText: s__('ClusterAgents|Read more about getting started'),
repositoryButtonText: s__('ClusterAgents|Go to the repository'),
primaryButtonText: s__('ClusterAgents|Connect with a GitLab Agent'), primaryButtonText: s__('ClusterAgents|Connect with a GitLab Agent'),
}; };
......
import produce from 'immer'; import produce from 'immer';
import { getAgentConfigPath } from '../clusters_util'; import { getAgentConfigPath } from '../clusters_util';
export const hasErrors = ({ errors = [] }) => errors?.length;
export function addAgentToStore(store, createClusterAgent, query, variables) { export function addAgentToStore(store, createClusterAgent, query, variables) {
if (!hasErrors(createClusterAgent)) {
const { clusterAgent } = createClusterAgent; const { clusterAgent } = createClusterAgent;
const sourceData = store.readQuery({ const sourceData = store.readQuery({
query, query,
...@@ -26,4 +29,36 @@ export function addAgentToStore(store, createClusterAgent, query, variables) { ...@@ -26,4 +29,36 @@ export function addAgentToStore(store, createClusterAgent, query, variables) {
variables, variables,
data, data,
}); });
}
}
export function addAgentConfigToStore(
store,
clusterAgentTokenCreate,
clusterAgent,
query,
variables,
) {
if (!hasErrors(clusterAgentTokenCreate)) {
const sourceData = store.readQuery({
query,
variables,
});
const data = produce(sourceData, (draftData) => {
const configuration = {
agentName: clusterAgent.name,
__typename: 'AgentConfiguration',
};
draftData.project.clusterAgents.nodes.push(clusterAgent);
draftData.project.agentConfigurations.nodes.push(configuration);
});
store.writeQuery({
query,
variables,
data,
});
}
} }
...@@ -7,20 +7,6 @@ ...@@ -7,20 +7,6 @@
} }
} }
.agents-empty-state {
.text-content {
@include gl-max-w-full;
@include media-breakpoint-up(lg) {
max-width: 70%;
}
}
.gl-alert-actions {
@include gl-mt-0;
@include gl-flex-wrap;
}
}
.gl-card-body { .gl-card-body {
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
@include gl-pt-2; @include gl-pt-2;
......
...@@ -14,7 +14,7 @@ module Resolvers ...@@ -14,7 +14,7 @@ module Resolvers
return [] unless can_read_agent_configuration? return [] unless can_read_agent_configuration?
kas_client.list_agent_config_files(project: project) kas_client.list_agent_config_files(project: project)
rescue GRPC::BadStatus => e rescue GRPC::BadStatus, Gitlab::Kas::Client::ConfigurationError => e
raise Gitlab::Graphql::Errors::ResourceNotAvailable, e.class.name raise Gitlab::Graphql::Errors::ResourceNotAvailable, e.class.name
end end
......
...@@ -7423,6 +7423,9 @@ msgstr "" ...@@ -7423,6 +7423,9 @@ msgstr ""
msgid "ClusterAgents|GitLab Agents provide an increased level of security when integrating with clusters. %{linkStart}Learn more about the GitLab Kubernetes Agent.%{linkEnd}" msgid "ClusterAgents|GitLab Agents provide an increased level of security when integrating with clusters. %{linkStart}Learn more about the GitLab Kubernetes Agent.%{linkEnd}"
msgstr "" msgstr ""
msgid "ClusterAgents|GitLab Kubernetes Agent"
msgstr ""
msgid "ClusterAgents|Go to the repository" msgid "ClusterAgents|Go to the repository"
msgstr "" msgstr ""
...@@ -7444,6 +7447,12 @@ msgstr "" ...@@ -7444,6 +7447,12 @@ msgstr ""
msgid "ClusterAgents|Learn how to troubleshoot" msgid "ClusterAgents|Learn how to troubleshoot"
msgstr "" msgstr ""
msgid "ClusterAgents|Learn more about installing a GitLab Kubernetes Agent"
msgstr ""
msgid "ClusterAgents|Learn more about the GitLab Kubernetes Agent registration."
msgstr ""
msgid "ClusterAgents|Learn more about the GitLab Kubernetes Agent." msgid "ClusterAgents|Learn more about the GitLab Kubernetes Agent."
msgstr "" msgstr ""
...@@ -7468,9 +7477,6 @@ msgstr "" ...@@ -7468,9 +7477,6 @@ msgstr ""
msgid "ClusterAgents|Not connected" msgid "ClusterAgents|Not connected"
msgstr "" msgstr ""
msgid "ClusterAgents|Read more about getting started"
msgstr ""
msgid "ClusterAgents|Recommended" msgid "ClusterAgents|Recommended"
msgstr "" msgstr ""
...@@ -7492,7 +7498,7 @@ msgstr "" ...@@ -7492,7 +7498,7 @@ msgstr ""
msgid "ClusterAgents|Select an Agent" msgid "ClusterAgents|Select an Agent"
msgstr "" msgstr ""
msgid "ClusterAgents|Select the Agent you want to register with GitLab and install on your cluster. To learn more about the Kubernetes Agent registration process %{linkStart}go to the documentation%{linkEnd}." msgid "ClusterAgents|Select an Agent to register with GitLab and install on your cluster."
msgstr "" msgstr ""
msgid "ClusterAgents|Select which Agent you want to install" msgid "ClusterAgents|Select which Agent you want to install"
...@@ -7501,6 +7507,9 @@ msgstr "" ...@@ -7501,6 +7507,9 @@ msgstr ""
msgid "ClusterAgents|The Agent has not been connected in a long time. There might be a connectivity issue. Last contact was %{timeAgo}." msgid "ClusterAgents|The Agent has not been connected in a long time. There might be a connectivity issue. Last contact was %{timeAgo}."
msgstr "" msgstr ""
msgid "ClusterAgents|The GitLab Agent also requires %{linkStart}enabling the Agent Server%{linkEnd}"
msgstr ""
msgid "ClusterAgents|The recommended installation method provided below includes the token. If you want to follow the alternative installation method provided in the docs make sure you save the token value before you close the window." msgid "ClusterAgents|The recommended installation method provided below includes the token. If you want to follow the alternative installation method provided in the docs make sure you save the token value before you close the window."
msgstr "" msgstr ""
......
...@@ -25,7 +25,7 @@ RSpec.describe 'Cluster agent registration', :js do ...@@ -25,7 +25,7 @@ RSpec.describe 'Cluster agent registration', :js do
it 'allows the user to select an agent to install, and displays the resulting agent token' do it 'allows the user to select an agent to install, and displays the resulting agent token' do
click_button('Actions') click_button('Actions')
expect(page).to have_content('Install new Agent') expect(page).to have_content('Register Agent')
click_button('Select an Agent') click_button('Select an Agent')
click_button('example-agent-2') click_button('example-agent-2')
......
import { GlAlert, GlEmptyState, GlSprintf } from '@gitlab/ui'; import { GlEmptyState, GlSprintf } from '@gitlab/ui';
import AgentEmptyState from '~/clusters_list/components/agent_empty_state.vue'; import AgentEmptyState from '~/clusters_list/components/agent_empty_state.vue';
import { INSTALL_AGENT_MODAL_ID } from '~/clusters_list/constants';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { helpPagePath } from '~/helpers/help_page_helper'; import { helpPagePath } from '~/helpers/help_page_helper';
const emptyStateImage = '/path/to/image'; const emptyStateImage = '/path/to/image';
const projectPath = 'path/to/project';
const multipleClustersDocsUrl = helpPagePath('user/project/clusters/multiple_kubernetes_clusters'); const multipleClustersDocsUrl = helpPagePath('user/project/clusters/multiple_kubernetes_clusters');
const installDocsUrl = helpPagePath('administration/clusters/kas'); const installDocsUrl = helpPagePath('administration/clusters/kas');
describe('AgentEmptyStateComponent', () => { describe('AgentEmptyStateComponent', () => {
let wrapper; let wrapper;
const propsData = {
hasConfigurations: false,
};
const provideData = { const provideData = {
emptyStateImage, emptyStateImage,
projectPath,
}; };
const findConfigurationsAlert = () => wrapper.findComponent(GlAlert);
const findMultipleClustersDocsLink = () => wrapper.findByTestId('multiple-clusters-docs-link'); const findMultipleClustersDocsLink = () => wrapper.findByTestId('multiple-clusters-docs-link');
const findInstallDocsLink = () => wrapper.findByTestId('install-docs-link'); const findInstallDocsLink = () => wrapper.findByTestId('install-docs-link');
const findIntegrationButton = () => wrapper.findByTestId('integration-primary-button'); const findIntegrationButton = () => wrapper.findByTestId('integration-primary-button');
...@@ -27,8 +22,10 @@ describe('AgentEmptyStateComponent', () => { ...@@ -27,8 +22,10 @@ describe('AgentEmptyStateComponent', () => {
beforeEach(() => { beforeEach(() => {
wrapper = shallowMountExtended(AgentEmptyState, { wrapper = shallowMountExtended(AgentEmptyState, {
propsData,
provide: provideData, provide: provideData,
directives: {
GlModalDirective: createMockDirective(),
},
stubs: { GlEmptyState, GlSprintf }, stubs: { GlEmptyState, GlSprintf },
}); });
}); });
...@@ -39,33 +36,22 @@ describe('AgentEmptyStateComponent', () => { ...@@ -39,33 +36,22 @@ describe('AgentEmptyStateComponent', () => {
} }
}); });
it('renders correct href attributes for the links', () => { it('renders the empty state', () => {
expect(findMultipleClustersDocsLink().attributes('href')).toBe(multipleClustersDocsUrl); expect(findEmptyState().exists()).toBe(true);
expect(findInstallDocsLink().attributes('href')).toBe(installDocsUrl);
}); });
describe('when there are no agent configurations in repository', () => { it('renders button for the agent registration', () => {
it('should render notification message box', () => { expect(findIntegrationButton().exists()).toBe(true);
expect(findConfigurationsAlert().exists()).toBe(true);
}); });
it('should disable integration button', () => { it('renders correct href attributes for the links', () => {
expect(findIntegrationButton().attributes('disabled')).toBe('true'); expect(findMultipleClustersDocsLink().attributes('href')).toBe(multipleClustersDocsUrl);
}); expect(findInstallDocsLink().attributes('href')).toBe(installDocsUrl);
}); });
describe('when there is a list of agent configurations', () => { it('renders correct modal id for the agent registration modal', () => {
beforeEach(() => { const binding = getBinding(findIntegrationButton().element, 'gl-modal-directive');
propsData.hasConfigurations = true;
wrapper = shallowMountExtended(AgentEmptyState, { expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
propsData,
provide: provideData,
});
});
it('should render content without notification message box', () => {
expect(findEmptyState().exists()).toBe(true);
expect(findConfigurationsAlert().exists()).toBe(false);
expect(findIntegrationButton().attributes('disabled')).toBeUndefined();
});
}); });
}); });
...@@ -19,7 +19,6 @@ describe('Agents', () => { ...@@ -19,7 +19,6 @@ describe('Agents', () => {
}; };
const provideData = { const provideData = {
projectPath: 'path/to/project', projectPath: 'path/to/project',
kasAddress: 'kas.example.com',
}; };
const createWrapper = ({ props = {}, agents = [], pageInfo = null, trees = [], count = 0 }) => { const createWrapper = ({ props = {}, agents = [], pageInfo = null, trees = [], count = 0 }) => {
...@@ -216,24 +215,6 @@ describe('Agents', () => { ...@@ -216,24 +215,6 @@ describe('Agents', () => {
}); });
}); });
describe('when the agent configurations are present', () => {
const trees = [
{
name: 'agent-1',
path: '.gitlab/agents/agent-1',
webPath: '/project/path/.gitlab/agents/agent-1',
},
];
beforeEach(() => {
return createWrapper({ agents: [], trees });
});
it('should pass the correct hasConfigurations boolean value to empty state component', () => {
expect(findEmptyState().props('hasConfigurations')).toEqual(true);
});
});
describe('when agents query has errored', () => { describe('when agents query has errored', () => {
beforeEach(() => { beforeEach(() => {
return createWrapper({ agents: null }); return createWrapper({ agents: null });
......
import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { createLocalVue, mount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue'; import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue';
import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '~/clusters_list/constants'; import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '~/clusters_list/constants';
import agentConfigurationsQuery from '~/clusters_list/graphql/queries/agent_configurations.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { agentConfigurationsResponse } from './mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('AvailableAgentsDropdown', () => { describe('AvailableAgentsDropdown', () => {
let wrapper; let wrapper;
...@@ -18,46 +11,19 @@ describe('AvailableAgentsDropdown', () => { ...@@ -18,46 +11,19 @@ describe('AvailableAgentsDropdown', () => {
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findConfiguredAgentItem = () => findDropdownItems().at(0); const findConfiguredAgentItem = () => findDropdownItems().at(0);
const createWrapper = ({ propsData = {}, isLoading = false }) => { const createWrapper = ({ propsData }) => {
const provide = { wrapper = shallowMount(AvailableAgentsDropdown, {
projectPath: 'path/to/project',
};
wrapper = (() => {
if (isLoading) {
const mocks = {
$apollo: {
queries: {
agents: {
loading: true,
},
},
},
};
return mount(AvailableAgentsDropdown, { mocks, provide, propsData });
}
const apolloProvider = createMockApollo([
[agentConfigurationsQuery, jest.fn().mockResolvedValue(agentConfigurationsResponse)],
]);
return mount(AvailableAgentsDropdown, {
localVue,
apolloProvider,
provide,
propsData, propsData,
}); });
})();
}; };
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('there are agents available', () => { describe('there are agents available', () => {
const propsData = { const propsData = {
availableAgents: ['configured-agent'],
isRegistering: false, isRegistering: false,
}; };
...@@ -69,12 +35,6 @@ describe('AvailableAgentsDropdown', () => { ...@@ -69,12 +35,6 @@ describe('AvailableAgentsDropdown', () => {
expect(findDropdown().props('text')).toBe(i18n.selectAgent); expect(findDropdown().props('text')).toBe(i18n.selectAgent);
}); });
it('shows only agents that are not yet installed', () => {
expect(findDropdownItems()).toHaveLength(1);
expect(findConfiguredAgentItem().text()).toBe('configured-agent');
expect(findConfiguredAgentItem().props('isChecked')).toBe(false);
});
describe('click events', () => { describe('click events', () => {
beforeEach(() => { beforeEach(() => {
findConfiguredAgentItem().vm.$emit('click'); findConfiguredAgentItem().vm.$emit('click');
...@@ -93,6 +53,7 @@ describe('AvailableAgentsDropdown', () => { ...@@ -93,6 +53,7 @@ describe('AvailableAgentsDropdown', () => {
describe('registration in progress', () => { describe('registration in progress', () => {
const propsData = { const propsData = {
availableAgents: ['configured-agent'],
isRegistering: true, isRegistering: true,
}; };
...@@ -108,22 +69,4 @@ describe('AvailableAgentsDropdown', () => { ...@@ -108,22 +69,4 @@ describe('AvailableAgentsDropdown', () => {
expect(findDropdown().props('loading')).toBe(true); expect(findDropdown().props('loading')).toBe(true);
}); });
}); });
describe('agents query is loading', () => {
const propsData = {
isRegistering: false,
};
beforeEach(() => {
createWrapper({ propsData, isLoading: true });
});
it('updates the text in the dropdown', () => {
expect(findDropdown().text()).toBe(i18n.selectAgent);
});
it('displays a loading icon', () => {
expect(findDropdown().props('loading')).toBe(true);
});
});
}); });
import { GlAlert, GlButton, GlFormInputGroup } from '@gitlab/ui'; import { GlAlert, GlButton, GlFormInputGroup } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue'; import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue';
import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue'; import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue';
import { I18N_INSTALL_AGENT_MODAL, MAX_LIST_COUNT } from '~/clusters_list/constants'; import { I18N_AGENT_MODAL, MAX_LIST_COUNT } from '~/clusters_list/constants';
import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql'; import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql';
import getAgentConfigurations from '~/clusters_list/graphql/queries/agent_configurations.query.graphql';
import createAgentMutation from '~/clusters_list/graphql/mutations/create_agent.mutation.graphql'; import createAgentMutation from '~/clusters_list/graphql/mutations/create_agent.mutation.graphql';
import createAgentTokenMutation from '~/clusters_list/graphql/mutations/create_agent_token.mutation.graphql'; import createAgentTokenMutation from '~/clusters_list/graphql/mutations/create_agent_token.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
...@@ -23,6 +25,9 @@ const localVue = createLocalVue(); ...@@ -23,6 +25,9 @@ const localVue = createLocalVue();
localVue.use(VueApollo); localVue.use(VueApollo);
const projectPath = 'path/to/project'; const projectPath = 'path/to/project';
const kasAddress = 'kas.example.com';
const kasEnabled = true;
const emptyStateImage = 'path/to/image';
const defaultBranchName = 'default'; const defaultBranchName = 'default';
const maxAgents = MAX_LIST_COUNT; const maxAgents = MAX_LIST_COUNT;
...@@ -30,7 +35,16 @@ describe('InstallAgentModal', () => { ...@@ -30,7 +35,16 @@ describe('InstallAgentModal', () => {
let wrapper; let wrapper;
let apolloProvider; let apolloProvider;
const i18n = I18N_INSTALL_AGENT_MODAL; const configurations = [{ agentName: 'agent-name' }];
const apolloQueryResponse = {
data: {
project: {
clusterAgents: { nodes: [] },
agentConfigurations: { nodes: configurations },
},
},
};
const findModal = () => wrapper.findComponent(ModalStub); const findModal = () => wrapper.findComponent(ModalStub);
const findAgentDropdown = () => findModal().findComponent(AvailableAgentsDropdown); const findAgentDropdown = () => findModal().findComponent(AvailableAgentsDropdown);
const findAlert = () => findModal().findComponent(GlAlert); const findAlert = () => findModal().findComponent(GlAlert);
...@@ -40,6 +54,8 @@ describe('InstallAgentModal', () => { ...@@ -40,6 +54,8 @@ describe('InstallAgentModal', () => {
.wrappers.find((button) => button.props('variant') === variant); .wrappers.find((button) => button.props('variant') === variant);
const findActionButton = () => findButtonByVariant('confirm'); const findActionButton = () => findButtonByVariant('confirm');
const findCancelButton = () => findButtonByVariant('default'); const findCancelButton = () => findButtonByVariant('default');
const findSecondaryButton = () => wrapper.findByTestId('agent-secondary-button');
const findImage = () => wrapper.findByRole('img', { alt: I18N_AGENT_MODAL.install.altText });
const expectDisabledAttribute = (element, disabled) => { const expectDisabledAttribute = (element, disabled) => {
if (disabled) { if (disabled) {
...@@ -52,7 +68,9 @@ describe('InstallAgentModal', () => { ...@@ -52,7 +68,9 @@ describe('InstallAgentModal', () => {
const createWrapper = () => { const createWrapper = () => {
const provide = { const provide = {
projectPath, projectPath,
kasAddress: 'kas.example.com', kasAddress,
kasEnabled,
emptyStateImage,
}; };
const propsData = { const propsData = {
...@@ -60,7 +78,7 @@ describe('InstallAgentModal', () => { ...@@ -60,7 +78,7 @@ describe('InstallAgentModal', () => {
maxAgents, maxAgents,
}; };
wrapper = shallowMount(InstallAgentModal, { wrapper = shallowMountExtended(InstallAgentModal, {
attachTo: document.body, attachTo: document.body,
stubs: { stubs: {
GlModal: ModalStub, GlModal: ModalStub,
...@@ -85,10 +103,12 @@ describe('InstallAgentModal', () => { ...@@ -85,10 +103,12 @@ describe('InstallAgentModal', () => {
}); });
}; };
const mockSelectedAgentResponse = () => { const mockSelectedAgentResponse = async () => {
createWrapper(); createWrapper();
writeQuery(); writeQuery();
await wrapper.vm.$nextTick();
wrapper.vm.setAgentName('agent-name'); wrapper.vm.setAgentName('agent-name');
findActionButton().vm.$emit('click'); findActionButton().vm.$emit('click');
...@@ -96,15 +116,20 @@ describe('InstallAgentModal', () => { ...@@ -96,15 +116,20 @@ describe('InstallAgentModal', () => {
}; };
beforeEach(() => { beforeEach(() => {
apolloProvider = createMockApollo([
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
]);
createWrapper(); createWrapper();
}); });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
apolloProvider = null; apolloProvider = null;
}); });
describe('when agent configurations are present', () => {
const i18n = I18N_AGENT_MODAL.register;
describe('initial state', () => { describe('initial state', () => {
it('renders the dropdown for available agents', () => { it('renders the dropdown for available agents', () => {
expect(findAgentDropdown().isVisible()).toBe(true); expect(findAgentDropdown().isVisible()).toBe(true);
...@@ -143,11 +168,12 @@ describe('InstallAgentModal', () => { ...@@ -143,11 +168,12 @@ describe('InstallAgentModal', () => {
beforeEach(() => { beforeEach(() => {
apolloProvider = createMockApollo([ apolloProvider = createMockApollo([
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
[createAgentMutation, createAgentHandler], [createAgentMutation, createAgentHandler],
[createAgentTokenMutation, createAgentTokenHandler], [createAgentTokenMutation, createAgentTokenHandler],
]); ]);
return mockSelectedAgentResponse(apolloProvider); return mockSelectedAgentResponse();
}); });
it('creates an agent and token', () => { it('creates an agent and token', () => {
...@@ -185,6 +211,7 @@ describe('InstallAgentModal', () => { ...@@ -185,6 +211,7 @@ describe('InstallAgentModal', () => {
describe('error creating agent', () => { describe('error creating agent', () => {
beforeEach(() => { beforeEach(() => {
apolloProvider = createMockApollo([ apolloProvider = createMockApollo([
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
[createAgentMutation, jest.fn().mockResolvedValue(createAgentErrorResponse)], [createAgentMutation, jest.fn().mockResolvedValue(createAgentErrorResponse)],
]); ]);
...@@ -192,13 +219,16 @@ describe('InstallAgentModal', () => { ...@@ -192,13 +219,16 @@ describe('InstallAgentModal', () => {
}); });
it('displays the error message', () => { it('displays the error message', () => {
expect(findAlert().text()).toBe(createAgentErrorResponse.data.createClusterAgent.errors[0]); expect(findAlert().text()).toBe(
createAgentErrorResponse.data.createClusterAgent.errors[0],
);
}); });
}); });
describe('error creating token', () => { describe('error creating token', () => {
beforeEach(() => { beforeEach(() => {
apolloProvider = createMockApollo([ apolloProvider = createMockApollo([
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryResponse)],
[createAgentMutation, jest.fn().mockResolvedValue(createAgentResponse)], [createAgentMutation, jest.fn().mockResolvedValue(createAgentResponse)],
[createAgentTokenMutation, jest.fn().mockResolvedValue(createAgentTokenErrorResponse)], [createAgentTokenMutation, jest.fn().mockResolvedValue(createAgentTokenErrorResponse)],
]); ]);
...@@ -206,11 +236,40 @@ describe('InstallAgentModal', () => { ...@@ -206,11 +236,40 @@ describe('InstallAgentModal', () => {
return mockSelectedAgentResponse(); return mockSelectedAgentResponse();
}); });
it('displays the error message', () => { it('displays the error message', async () => {
expect(findAlert().text()).toBe( expect(findAlert().text()).toBe(
createAgentTokenErrorResponse.data.clusterAgentTokenCreate.errors[0], createAgentTokenErrorResponse.data.clusterAgentTokenCreate.errors[0],
); );
}); });
}); });
}); });
});
describe('when there are no agent configurations present', () => {
const i18n = I18N_AGENT_MODAL.install;
const apolloQueryEmptyResponse = {
data: {
project: {
clusterAgents: { nodes: [] },
agentConfigurations: { nodes: [] },
},
},
};
beforeEach(() => {
apolloProvider = createMockApollo([
[getAgentConfigurations, jest.fn().mockResolvedValue(apolloQueryEmptyResponse)],
]);
createWrapper();
});
it('renders empty state image', () => {
expect(findImage().attributes('src')).toBe(emptyStateImage);
});
it('renders a secondary button', () => {
expect(findSecondaryButton().isVisible()).toBe(true);
expect(findSecondaryButton().text()).toBe(i18n.secondaryButton);
});
});
}); });
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