Commit e1dd2d51 authored by Phil Hughes's avatar Phil Hughes

Merge branch...

Merge branch '347240-change-the-ui-to-support-agent-registration-with-and-without-a-config' into 'master'

Change the UI to support agent registration with and without a config

See merge request gitlab-org/gitlab!82036
parents bfeb5f25 388f87fa
<script>
import { GlLink, GlTable, GlIcon, GlSprintf, GlTooltip, GlPopover } from '@gitlab/ui';
import {
GlLink,
GlTable,
GlIcon,
GlSprintf,
GlTooltip,
GlTooltipDirective,
GlPopover,
} from '@gitlab/ui';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { helpPagePath } from '~/helpers/help_page_helper';
......@@ -19,12 +27,18 @@ export default {
TimeAgoTooltip,
DeleteAgentButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [timeagoMixin],
AGENT_STATUSES,
troubleshootingLink: helpPagePath('user/clusters/agent/troubleshooting'),
versionUpdateLink: helpPagePath('user/clusters/agent/install/index', {
anchor: 'update-the-agent-version',
}),
configHelpLink: helpPagePath('user/clusters/agent/install/index', {
anchor: 'create-an-agent-without-configuration-file',
}),
inject: ['gitlabVersion'],
props: {
agents: {
......@@ -256,7 +270,16 @@ export default {
{{ getAgentConfigPath(item.name) }}
</gl-link>
<span v-else>{{ getAgentConfigPath(item.name) }}</span>
<span v-else
>{{ $options.i18n.defaultConfigText }}
<gl-link
v-gl-tooltip
:href="$options.configHelpLink"
:title="$options.i18n.defaultConfigTooltip"
:aria-label="$options.i18n.defaultConfigTooltip"
class="gl-vertical-align-middle"
><gl-icon name="question" :size="14" /></gl-link
></span>
</span>
</template>
......
......@@ -116,9 +116,6 @@ export default {
},
},
methods: {
reloadAgents() {
this.$apollo.queries.agents.refetch();
},
nextPage() {
this.cursor = {
first: MAX_LIST_COUNT,
......
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlSearchBoxByType,
GlSprintf,
} from '@gitlab/ui';
import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '../constants';
export default {
......@@ -8,6 +14,9 @@ export default {
components: {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlSearchBoxByType,
GlSprintf,
},
props: {
isRegistering: {
......@@ -22,6 +31,7 @@ export default {
data() {
return {
selectedAgent: null,
searchTerm: '',
};
},
computed: {
......@@ -34,22 +44,45 @@ export default {
return this.selectedAgent;
},
shouldRenderCreateButton() {
return this.searchTerm && !this.availableAgents.includes(this.searchTerm);
},
filteredResults() {
const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
return this.availableAgents.filter((resultString) =>
resultString.toLowerCase().includes(lowerCasedSearchTerm),
);
},
},
methods: {
selectAgent(agent) {
this.$emit('agentSelected', agent);
this.selectedAgent = agent;
this.clearSearch();
},
isSelected(agent) {
return this.selectedAgent === agent;
},
clearSearch() {
this.searchTerm = '';
},
focusSearch() {
this.$refs.searchInput.focusInput();
},
handleShow() {
this.clearSearch();
this.focusSearch();
},
},
};
</script>
<template>
<gl-dropdown :text="dropdownText" :loading="isRegistering">
<gl-dropdown :text="dropdownText" :loading="isRegistering" @shown="handleShow">
<template #header>
<gl-search-box-by-type ref="searchInput" v-model.trim="searchTerm" />
</template>
<gl-dropdown-item
v-for="agent in availableAgents"
v-for="agent in filteredResults"
:key="agent"
:is-checked="isSelected(agent)"
is-check-item
......@@ -57,5 +90,16 @@ export default {
>
{{ agent }}
</gl-dropdown-item>
<gl-dropdown-item v-if="!filteredResults.length" ref="noMatchingResults">{{
$options.i18n.noResults
}}</gl-dropdown-item>
<template v-if="shouldRenderCreateButton">
<gl-dropdown-divider />
<gl-dropdown-item data-testid="create-config-button" @click="selectAgent(searchTerm)">
<gl-sprintf :message="$options.i18n.createButton">
<template #searchTerm>{{ searchTerm }}</template>
</gl-sprintf>
</gl-dropdown-item>
</template>
</gl-dropdown>
</template>
......@@ -35,6 +35,7 @@ const trackingMixin = Tracking.mixin({ label: EVENT_LABEL_MODAL });
export default {
modalId: INSTALL_AGENT_MODAL_ID,
i18n: I18N_AGENT_MODAL,
EVENT_ACTIONS_OPEN,
EVENT_ACTIONS_CLICK,
EVENT_LABEL_MODAL,
......@@ -45,7 +46,6 @@ export default {
anchor: 'advanced-installation',
}),
enableKasPath: helpPagePath('administration/clusters/kas'),
installAgentPath: helpPagePath('user/clusters/agent/install/index'),
registerAgentPath: helpPagePath('user/clusters/agent/install/index', {
anchor: 'register-an-agent-with-gitlab',
}),
......@@ -109,10 +109,10 @@ export default {
return !this.registering && this.agentName !== null;
},
canCancel() {
return !this.registered && !this.registering && this.isAgentRegistrationModal;
return !this.registered && !this.registering && !this.kasDisabled;
},
canRegister() {
return !this.registered && this.isAgentRegistrationModal;
return !this.registered && !this.kasDisabled;
},
agentRegistrationCommand() {
return generateAgentRegistrationCommand(this.agentToken, this.kasAddress);
......@@ -125,32 +125,20 @@ export default {
projectPath: this.projectPath,
};
},
i18n() {
return I18N_AGENT_MODAL[this.modalType];
},
repositoryPath() {
return `/${this.projectPath}`;
},
modalType() {
return !this.availableAgents?.length && !this.registered
? MODAL_TYPE_EMPTY
: MODAL_TYPE_REGISTER;
return this.kasDisabled ? MODAL_TYPE_EMPTY : MODAL_TYPE_REGISTER;
},
modalSize() {
return this.isEmptyStateModal ? 'sm' : 'md';
},
isEmptyStateModal() {
return this.modalType === MODAL_TYPE_EMPTY;
},
isAgentRegistrationModal() {
return this.modalType === MODAL_TYPE_REGISTER;
},
isKasEnabledInEmptyStateModal() {
return this.isEmptyStateModal && !this.kasDisabled;
return this.kasDisabled ? 'sm' : 'md';
},
},
methods: {
setAgentName(name) {
this.error = null;
this.agentName = name;
this.track(EVENT_ACTIONS_SELECT);
},
......@@ -244,7 +232,7 @@ export default {
if (error) {
this.error = error.message;
} else {
this.error = this.i18n.unknownError;
this.error = this.$options.i18n.unknownError;
}
} finally {
this.registering = false;
......@@ -258,22 +246,21 @@ export default {
<gl-modal
ref="modal"
:modal-id="$options.modalId"
:title="i18n.modalTitle"
:title="$options.i18n.modalTitle"
:size="modalSize"
static
lazy
@hidden="resetModal"
@show="track($options.EVENT_ACTIONS_OPEN, { property: modalType })"
>
<template v-if="isAgentRegistrationModal">
<template v-if="!kasDisabled">
<template v-if="!registered">
<p>
<strong>{{ i18n.selectAgentTitle }}</strong>
</p>
<p class="gl-mb-0">{{ i18n.selectAgentBody }}</p>
<p>
<gl-link :href="$options.registerAgentPath"> {{ i18n.learnMoreLink }}</gl-link>
<p class="gl-mb-0">
<gl-sprintf :message="$options.i18n.modalBody">
<template #link="{ content }">
<gl-link :href="repositoryPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<form>
......@@ -287,8 +274,16 @@ export default {
</gl-form-group>
</form>
<p>
<gl-link :href="$options.registerAgentPath"> {{ $options.i18n.learnMoreLink }}</gl-link>
</p>
<p v-if="error">
<gl-alert :title="i18n.registrationErrorTitle" variant="danger" :dismissible="false">
<gl-alert
:title="$options.i18n.registrationErrorTitle"
variant="danger"
:dismissible="false"
>
{{ error }}
</gl-alert>
</p>
......@@ -296,11 +291,11 @@ export default {
<template v-else>
<p>
<strong>{{ i18n.tokenTitle }}</strong>
<strong>{{ $options.i18n.tokenTitle }}</strong>
</p>
<p>
<gl-sprintf :message="i18n.tokenBody">
<gl-sprintf :message="$options.i18n.tokenBody">
<template #link="{ content }">
<gl-link :href="$options.basicInstallPath" target="_blank"> {{ content }}</gl-link>
</template>
......@@ -308,8 +303,12 @@ export default {
</p>
<p>
<gl-alert :title="i18n.tokenSingleUseWarningTitle" variant="warning" :dismissible="false">
{{ i18n.tokenSingleUseWarningBody }}
<gl-alert
:title="$options.i18n.tokenSingleUseWarningTitle"
variant="warning"
:dismissible="false"
>
{{ $options.i18n.tokenSingleUseWarningBody }}
</gl-alert>
</p>
......@@ -318,7 +317,7 @@ export default {
<template #append>
<modal-copy-button
:text="agentToken"
:title="i18n.copyToken"
:title="$options.i18n.copyToken"
:modal-id="$options.modalId"
/>
</template>
......@@ -326,11 +325,11 @@ export default {
</p>
<p>
<strong>{{ i18n.basicInstallTitle }}</strong>
<strong>{{ $options.i18n.basicInstallTitle }}</strong>
</p>
<p>
{{ i18n.basicInstallBody }}
{{ $options.i18n.basicInstallBody }}
</p>
<p>
......@@ -338,11 +337,11 @@ export default {
</p>
<p>
<strong>{{ i18n.advancedInstallTitle }}</strong>
<strong>{{ $options.i18n.advancedInstallTitle }}</strong>
</p>
<p>
<gl-sprintf :message="i18n.advancedInstallBody">
<gl-sprintf :message="$options.i18n.advancedInstallBody">
<template #link="{ content }">
<gl-link :href="$options.advancedInstallPath" target="_blank"> {{ content }}</gl-link>
</template>
......@@ -353,24 +352,16 @@ export default {
<template v-else>
<div class="gl-text-center gl-mb-5">
<img :alt="i18n.altText" :src="emptyStateImage" height="100" />
<img :alt="$options.i18n.altText" :src="emptyStateImage" height="100" />
</div>
<p v-if="kasDisabled">
<gl-sprintf :message="i18n.enableKasText">
<gl-sprintf :message="$options.i18n.enableKasText">
<template #link="{ content }">
<gl-link :href="$options.enableKasPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<p v-else>
<gl-sprintf :message="i18n.modalBody">
<template #link="{ content }">
<gl-link :href="$options.installAgentPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</template>
<template #modal-footer>
......@@ -382,7 +373,7 @@ export default {
:data-track-label="$options.EVENT_LABEL_MODAL"
data-track-property="close"
@click="closeModal"
>{{ i18n.close }}
>{{ $options.i18n.close }}
</gl-button>
<gl-button
......@@ -391,7 +382,7 @@ export default {
:data-track-label="$options.EVENT_LABEL_MODAL"
data-track-property="cancel"
@click="closeModal"
>{{ i18n.cancel }}
>{{ $options.i18n.cancel }}
</gl-button>
<gl-button
......@@ -403,25 +394,16 @@ export default {
:data-track-label="$options.EVENT_LABEL_MODAL"
data-track-property="register"
@click="registerAgent"
>{{ i18n.registerAgentButton }}
>{{ $options.i18n.registerAgentButton }}
</gl-button>
<gl-button
v-if="isEmptyStateModal"
v-if="kasDisabled"
:data-track-action="$options.EVENT_ACTIONS_CLICK"
:data-track-label="$options.EVENT_LABEL_MODAL"
data-track-property="done"
@click="closeModal"
>{{ i18n.done }}
</gl-button>
<gl-button
v-if="isKasEnabledInEmptyStateModal"
:href="repositoryPath"
variant="confirm"
category="primary"
data-testid="agent-primary-button"
>{{ i18n.primaryButton }}
>{{ $options.i18n.close }}
</gl-button>
</template>
</gl-modal>
......
......@@ -83,21 +83,24 @@ export const I18N_AGENT_TABLE = {
),
versionMismatchOutdatedTitle: s__('ClusterAgents|Agent version mismatch and update'),
viewDocsText: s__('ClusterAgents|How to update an agent?'),
defaultConfigText: s__('ClusterAgents|Default configuration'),
defaultConfigTooltip: s__('ClusterAgents|What is default configuration?'),
};
export const I18N_AGENT_MODAL = {
agent_registration: {
registerAgentButton: s__('ClusterAgents|Register'),
close: __('Close'),
cancel: __('Cancel'),
modalTitle: s__('ClusterAgents|Connect a cluster through an agent'),
selectAgentTitle: s__('ClusterAgents|Select an agent to register with GitLab'),
selectAgentBody: s__(
'ClusterAgents|Register an agent to generate a token that will be used to install the agent on your cluster in the next step.',
modalBody: s__(
'ClusterAgents|Add an agent configuration file to %{linkStart}this repository%{linkEnd} and select it, or create a new one to register with GitLab:',
),
learnMoreLink: s__('ClusterAgents|How to register an agent?'),
enableKasText: s__(
"ClusterAgents|Your instance doesn't have the %{linkStart}GitLab Agent Server (KAS)%{linkEnd} set up. Ask a GitLab Administrator to install it.",
),
altText: s__('ClusterAgents|GitLab Agent for Kubernetes'),
learnMoreLink: s__('ClusterAgents|How do I register an agent?'),
copyToken: s__('ClusterAgents|Copy token'),
tokenTitle: s__('ClusterAgents|Registration token'),
tokenBody: s__(
......@@ -123,26 +126,15 @@ export const I18N_AGENT_MODAL = {
registrationErrorTitle: s__('ClusterAgents|Failed to register an agent'),
unknownError: s__('ClusterAgents|An unknown error occurred. Please try again.'),
},
empty_state: {
modalTitle: s__('ClusterAgents|Connect your cluster through an agent'),
modalBody: s__(
"ClusterAgents|To install a new agent, first add the agent's configuration file to this repository. %{linkStart}Learn more about installing GitLab Agent.%{linkEnd}",
),
enableKasText: s__(
"ClusterAgents|Your instance doesn't have the %{linkStart}GitLab Agent Server (KAS)%{linkEnd} set up. Ask a GitLab Administrator to install it.",
),
altText: s__('ClusterAgents|GitLab Agent for Kubernetes'),
primaryButton: s__('ClusterAgents|Go to the repository files'),
done: __('Cancel'),
},
};
export const KAS_DISABLED_ERROR = 'Gitlab::Kas::Client::ConfigurationError';
export const I18N_AVAILABLE_AGENTS_DROPDOWN = {
selectAgent: s__('ClusterAgents|Select an agent'),
registeringAgent: s__('ClusterAgents|Registering Agent'),
selectAgent: s__('ClusterAgents|Select an agent or enter a name to create new'),
registeringAgent: s__('ClusterAgents|Registering agent'),
noResults: __('No matching results'),
createButton: s__('ClusterAgents|Create agent: %{searchTerm}'),
};
export const AGENT_STATUSES = {
......
import produce from 'immer';
import { getAgentConfigPath } from '../clusters_util';
export const hasErrors = ({ errors = [] }) => errors?.length;
......@@ -12,17 +11,8 @@ export function addAgentToStore(store, createClusterAgent, query, variables) {
});
const data = produce(sourceData, (draftData) => {
const configuration = {
id: clusterAgent.id,
name: clusterAgent.name,
path: getAgentConfigPath(clusterAgent.name),
webPath: clusterAgent.webPath,
__typename: 'TreeEntry',
};
draftData.project.clusterAgents.nodes.push(clusterAgent);
draftData.project.clusterAgents.count += 1;
draftData.project.repository.tree.trees.nodes.push(configuration);
});
store.writeQuery({
......
......@@ -7,9 +7,7 @@ query getAgents(
$first: Int
$last: Int
$afterAgent: String
$afterTree: String
$beforeAgent: String
$beforeTree: String
) {
project(fullPath: $projectPath) {
id
......@@ -27,17 +25,13 @@ query getAgents(
repository {
tree(path: ".gitlab/agents", ref: $defaultBranchName) {
trees(first: $first, last: $last, after: $afterTree, before: $beforeTree) {
trees {
nodes {
id
name
path
webPath
}
pageInfo {
...PageInfo
}
}
}
}
......
......@@ -27,18 +27,47 @@ Before you can install the agent in your cluster, you need:
To install the agent in your cluster:
1. [Create an agent configuration file called `config.yaml`](#create-an-agent-configuration-file).
1. [Register the agent with GitLab](#register-the-agent-with-gitlab).
1. [Install the agent in your cluster](#install-the-agent-in-the-cluster).
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch a GitLab 14.2 [walk-through of this process](https://www.youtube.com/watch?v=XuBpKtsgGkE).
### Register the agent with GitLab
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5786) in GitLab 14.1, you can create a new agent record directly from the GitLab UI.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/347240) in GitLab 14.9, the agent can be registered without creating an agent configuration file.
You must register an agent with GitLab.
Prerequisites:
- For a [GitLab CI/CD workflow](../ci_cd_tunnel.md), ensure that
[GitLab CI/CD is enabled](../../../../ci/enable_or_disable_ci.md#enable-cicd-in-a-project).
To register an agent with GitLab:
1. On the top bar, select **Menu > Projects** and find your project.
1. From the left sidebar, select **Infrastructure > Kubernetes clusters**.
1. Select **Actions**.
1. From the **Select an agent** dropdown list:
- If you want to create a configuration with CI/CD defaults, type a name for the agent.
- If you already have an [agent configuration file](#create-an-agent-configuration-file), select it from the list.
1. Select **Register an agent**.
1. GitLab generates a registration token for this agent. Securely store this secret token. You need it to install the agent in your cluster and to [update the agent](#update-the-agent-version) to another version.
1. Copy the command under **Recommended installation method**. You need it when you use the one-liner installation method to install the agent in your cluster.
### Create an agent configuration file
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/259669) in GitLab 13.7, the agent configuration file can be added to multiple directories (or subdirectories) of the repository.
> - Group authorization was [introduced](https://gitlab.com/groups/gitlab-org/-/epics/5784) in GitLab 14.3.
In a GitLab project, in the repository, create a file called `config.yaml` at this path:
You can use an agent configuration file to specify details about your implementation.
Creating a file is optional but is needed if:
- You use [a GitOps workflow](../gitops.md#gitops-configuration-reference) and you want a more advanced configuration.
- You use a GitLab CI/CD workflow. In that workflow, you must [authorize the agent](../ci_cd_tunnel.md#authorize-the-agent).
To create an agent configuration file, go to the GitLab project. In the repository, create a file called `config.yaml` at this path:
```plaintext
.gitlab/agents/<agent-name>/config.yaml
......@@ -54,28 +83,6 @@ The agent bootstraps with the GitLab installation URL and an authentication toke
and you provide the rest of the configuration in your repository, following
Infrastructure as Code (IaaC) best practices.
### Register the agent with GitLab
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/5786) in GitLab 14.1, you can create a new agent record directly from the GitLab UI.
Now that you've created your agent configuration file, register it
with GitLab.
When you register the agent, GitLab generates a token that you need to
install the agent in your cluster.
Prerequisite when using a [GitLab CI/CD workflow](../ci_cd_tunnel.md):
- In the project that has the agent configuration file, ensure that [GitLab CI/CD is enabled](../../../../ci/enable_or_disable_ci.md#enable-cicd-in-a-project).
To register the agent with GitLab:
1. On the top bar, select **Menu > Projects** and find the project that has your agent configuration file.
1. From the left sidebar, select **Infrastructure > Kubernetes clusters**.
1. Select **Actions**.
1. From the **Select an agent** dropdown list, select the agent you want to register and select **Register an agent**.
1. GitLab generates a registration token for this agent. Securely store this secret token. You need it to install the agent in your cluster and to [update the agent](#update-the-agent-version) to another version.
1. Copy the command under **Recommended installation method**. You need it when you use the one-liner installation method to install the agent in your cluster.
### Install the agent in the cluster
To connect your cluster to GitLab, install the registered agent
......
......@@ -7670,6 +7670,9 @@ msgstr ""
msgid "ClusterAgents|Actions"
msgstr ""
msgid "ClusterAgents|Add an agent configuration file to %{linkStart}this repository%{linkEnd} and select it, or create a new one to register with GitLab:"
msgstr ""
msgid "ClusterAgents|Advanced installation methods"
msgstr ""
......@@ -7736,9 +7739,6 @@ msgstr ""
msgid "ClusterAgents|Connect with the GitLab Agent"
msgstr ""
msgid "ClusterAgents|Connect your cluster through an agent"
msgstr ""
msgid "ClusterAgents|Connected"
msgstr ""
......@@ -7751,6 +7751,9 @@ msgstr ""
msgid "ClusterAgents|Create a new cluster"
msgstr ""
msgid "ClusterAgents|Create agent: %{searchTerm}"
msgstr ""
msgid "ClusterAgents|Created by"
msgstr ""
......@@ -7760,6 +7763,9 @@ msgstr ""
msgid "ClusterAgents|Date created"
msgstr ""
msgid "ClusterAgents|Default configuration"
msgstr ""
msgid "ClusterAgents|Delete"
msgstr ""
......@@ -7790,10 +7796,7 @@ msgstr ""
msgid "ClusterAgents|Give feedback"
msgstr ""
msgid "ClusterAgents|Go to the repository files"
msgstr ""
msgid "ClusterAgents|How to register an agent?"
msgid "ClusterAgents|How do I register an agent?"
msgstr ""
msgid "ClusterAgents|How to update an agent?"
......@@ -7844,10 +7847,7 @@ msgstr ""
msgid "ClusterAgents|Register"
msgstr ""
msgid "ClusterAgents|Register an agent to generate a token that will be used to install the agent on your cluster in the next step."
msgstr ""
msgid "ClusterAgents|Registering Agent"
msgid "ClusterAgents|Registering agent"
msgstr ""
msgid "ClusterAgents|Registration token"
......@@ -7871,10 +7871,7 @@ msgstr ""
msgid "ClusterAgents|See Agent activity updates such as tokens created or revoked and clusters connected or not connected."
msgstr ""
msgid "ClusterAgents|Select an agent"
msgstr ""
msgid "ClusterAgents|Select an agent to register with GitLab"
msgid "ClusterAgents|Select an agent or enter a name to create new"
msgstr ""
msgid "ClusterAgents|Tell us what you think"
......@@ -7906,9 +7903,6 @@ msgstr ""
msgid "ClusterAgents|To delete the agent, type %{name} to confirm:"
msgstr ""
msgid "ClusterAgents|To install a new agent, first add the agent's configuration file to this repository. %{linkStart}Learn more about installing GitLab Agent.%{linkEnd}"
msgstr ""
msgid "ClusterAgents|Token created by %{userName}"
msgstr ""
......@@ -7930,6 +7924,9 @@ msgstr ""
msgid "ClusterAgents|What is GitLab Agent activity?"
msgstr ""
msgid "ClusterAgents|What is default configuration?"
msgstr ""
msgid "ClusterAgents|You cannot see this token again after you close this window."
msgstr ""
......
......@@ -8,6 +8,9 @@ import { stubComponent } from 'helpers/stub_component';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { clusterAgents, connectedTimeNow, connectedTimeInactive } from './mock_data';
const defaultConfigHelpUrl =
'/help/user/clusters/agent/install/index#create-an-agent-without-configuration-file';
const provideData = {
gitlabVersion: '14.8',
};
......@@ -31,8 +34,8 @@ describe('AgentTable', () => {
let wrapper;
const findAgentLink = (at) => wrapper.findAllByTestId('cluster-agent-name-link').at(at);
const findStatusIcon = (at) => wrapper.findAllComponents(GlIcon).at(at);
const findStatusText = (at) => wrapper.findAllByTestId('cluster-agent-connection-status').at(at);
const findStatusIcon = (at) => findStatusText(at).find(GlIcon);
const findLastContactText = (at) => wrapper.findAllByTestId('cluster-agent-last-contact').at(at);
const findVersionText = (at) => wrapper.findAllByTestId('cluster-agent-version').at(at);
const findConfiguration = (at) =>
......@@ -141,16 +144,16 @@ describe('AgentTable', () => {
);
it.each`
agentPath | hasLink | lineNumber
${'.gitlab/agents/agent-1'} | ${true} | ${0}
${'.gitlab/agents/agent-2'} | ${false} | ${1}
agentConfig | link | lineNumber
${'.gitlab/agents/agent-1'} | ${'/agent/full/path'} | ${0}
${'Default configuration'} | ${defaultConfigHelpUrl} | ${1}
`(
'displays config file path as "$agentPath" at line $lineNumber',
({ agentPath, hasLink, lineNumber }) => {
({ agentConfig, link, lineNumber }) => {
const findLink = findConfiguration(lineNumber).find(GlLink);
expect(findLink.exists()).toBe(hasLink);
expect(findConfiguration(lineNumber).text()).toBe(agentPath);
expect(findLink.attributes('href')).toBe(link);
expect(findConfiguration(lineNumber).text()).toBe(agentConfig);
},
);
......
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue';
import { I18N_AVAILABLE_AGENTS_DROPDOWN } from '~/clusters_list/constants';
......@@ -9,11 +9,14 @@ describe('AvailableAgentsDropdown', () => {
const i18n = I18N_AVAILABLE_AGENTS_DROPDOWN;
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findConfiguredAgentItem = () => findDropdownItems().at(0);
const findFirstAgentItem = () => findDropdownItems().at(0);
const findSearchInput = () => wrapper.findComponent(GlSearchBoxByType);
const findCreateButton = () => wrapper.findByTestId('create-config-button');
const createWrapper = ({ propsData }) => {
wrapper = shallowMount(AvailableAgentsDropdown, {
wrapper = shallowMountExtended(AvailableAgentsDropdown, {
propsData,
stubs: { GlDropdown },
});
};
......@@ -23,7 +26,7 @@ describe('AvailableAgentsDropdown', () => {
describe('there are agents available', () => {
const propsData = {
availableAgents: ['configured-agent'],
availableAgents: ['configured-agent', 'search-agent', 'test-agent'],
isRegistering: false,
};
......@@ -35,9 +38,38 @@ describe('AvailableAgentsDropdown', () => {
expect(findDropdown().props('text')).toBe(i18n.selectAgent);
});
describe('click events', () => {
describe('search agent', () => {
it('renders search button', () => {
expect(findSearchInput().exists()).toBe(true);
});
it('renders all agents when search term is empty', () => {
expect(findDropdownItems()).toHaveLength(3);
});
it('renders only the agent searched for when the search item exists', async () => {
await findSearchInput().vm.$emit('input', 'search-agent');
expect(findDropdownItems()).toHaveLength(1);
expect(findFirstAgentItem().text()).toBe('search-agent');
});
it('renders create button when search started', async () => {
await findSearchInput().vm.$emit('input', 'new-agent');
expect(findCreateButton().exists()).toBe(true);
});
it("doesn't render create button when search item is found", async () => {
await findSearchInput().vm.$emit('input', 'search-agent');
expect(findCreateButton().exists()).toBe(false);
});
});
describe('select existing agent configuration', () => {
beforeEach(() => {
findConfiguredAgentItem().vm.$emit('click');
findFirstAgentItem().vm.$emit('click');
});
it('emits agentSelected with the name of the clicked agent', () => {
......@@ -46,7 +78,22 @@ describe('AvailableAgentsDropdown', () => {
it('marks the clicked item as selected', () => {
expect(findDropdown().props('text')).toBe('configured-agent');
expect(findConfiguredAgentItem().props('isChecked')).toBe(true);
expect(findFirstAgentItem().props('isChecked')).toBe(true);
});
});
describe('create new agent configuration', () => {
beforeEach(async () => {
await findSearchInput().vm.$emit('input', 'new-agent');
findCreateButton().vm.$emit('click');
});
it('emits agentSelected with the name of the clicked agent', () => {
expect(wrapper.emitted('agentSelected')).toEqual([['new-agent']]);
});
it('marks the clicked item as selected', () => {
expect(findDropdown().props('text')).toBe('new-agent');
});
});
});
......
......@@ -39,6 +39,7 @@ const kasAddress = 'kas.example.com';
const emptyStateImage = 'path/to/image';
const defaultBranchName = 'default';
const maxAgents = MAX_LIST_COUNT;
const i18n = I18N_AGENT_MODAL;
describe('InstallAgentModal', () => {
let wrapper;
......@@ -67,7 +68,7 @@ describe('InstallAgentModal', () => {
const findActionButton = () => findButtonByVariant('confirm');
const findCancelButton = () => findButtonByVariant('default');
const findPrimaryButton = () => wrapper.findByTestId('agent-primary-button');
const findImage = () => wrapper.findByRole('img', { alt: I18N_AGENT_MODAL.empty_state.altText });
const findImage = () => wrapper.findByRole('img', { alt: i18n.altText });
const expectDisabledAttribute = (element, disabled) => {
if (disabled) {
......@@ -140,12 +141,13 @@ describe('InstallAgentModal', () => {
apolloProvider = null;
});
describe('when agent configurations are present', () => {
const i18n = I18N_AGENT_MODAL.agent_registration;
describe('when KAS is enabled', () => {
describe('initial state', () => {
it('renders the dropdown for available agents', () => {
expect(findAgentDropdown().isVisible()).toBe(true);
});
it("doesn't render agent installation instructions", () => {
expect(findModal().text()).not.toContain(i18n.basicInstallTitle);
expect(findModal().findComponent(GlFormInputGroup).exists()).toBe(false);
expect(findModal().findComponent(GlAlert).exists()).toBe(false);
......@@ -272,44 +274,7 @@ describe('InstallAgentModal', () => {
});
});
describe('when there are no agent configurations present', () => {
const i18n = I18N_AGENT_MODAL.empty_state;
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 primary button', () => {
expect(findPrimaryButton().isVisible()).toBe(true);
expect(findPrimaryButton().text()).toBe(i18n.primaryButton);
});
it('sends the event with the modalType', () => {
findModal().vm.$emit('show');
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_OPEN, {
label: EVENT_LABEL_MODAL,
property: MODAL_TYPE_EMPTY,
});
});
});
describe('when KAS is disabled', () => {
const i18n = I18N_AGENT_MODAL.empty_state;
beforeEach(async () => {
apolloProvider = createMockApollo([
[getAgentConfigurations, jest.fn().mockResolvedValue(kasDisabledErrorResponse)],
......@@ -331,11 +296,19 @@ describe('InstallAgentModal', () => {
it('renders a cancel button', () => {
expect(findCancelButton().isVisible()).toBe(true);
expect(findCancelButton().text()).toBe(i18n.done);
expect(findCancelButton().text()).toBe(i18n.close);
});
it("doesn't render a secondary button", () => {
expect(findPrimaryButton().exists()).toBe(false);
});
it('sends the event with the modalType', () => {
findModal().vm.$emit('show');
expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTIONS_OPEN, {
label: EVENT_LABEL_MODAL,
property: MODAL_TYPE_EMPTY,
});
});
});
});
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