Commit f76a42a5 authored by Anna Vovchenko's avatar Anna Vovchenko Committed by Nicolò Maria Mezzopera

Move navigation and actions on clusters page to Vue

parent 0fe05e70
......@@ -6,3 +6,7 @@ export function generateAgentRegistrationCommand(agentToken, kasAddress) {
--agent-version stable \\
--namespace gitlab-kubernetes-agent | kubectl apply -f -`;
}
export function getAgentConfigPath(clusterAgentName) {
return `.gitlab/agents/${clusterAgentName}`;
}
<script>
import { GlButton, GlEmptyState, GlLink, GlSprintf, GlAlert, GlModalDirective } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { INSTALL_AGENT_MODAL_ID, I18N_AGENTS_EMPTY_STATE } from '../constants';
export default {
i18n: I18N_AGENTS_EMPTY_STATE,
modalId: INSTALL_AGENT_MODAL_ID,
multipleClustersDocsUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
installDocsUrl: helpPagePath('administration/clusters/kas'),
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,
......@@ -15,14 +24,7 @@ export default {
directives: {
GlModalDirective,
},
inject: [
'emptyStateImage',
'projectPath',
'multipleClustersDocsUrl',
'installDocsUrl',
'getStartedDocsUrl',
'integrationDocsUrl',
],
inject: ['emptyStateImage', 'projectPath'],
props: {
hasConfigurations: {
type: Boolean,
......@@ -47,7 +49,7 @@ export default {
<gl-sprintf :message="$options.i18n.multipleClustersText">
<template #link="{ content }">
<gl-link
:href="multipleClustersDocsUrl"
:href="$options.multipleClustersDocsUrl"
target="_blank"
data-testid="multiple-clusters-docs-link"
>
......@@ -58,7 +60,7 @@ export default {
</p>
<p class="mw-460 gl-mx-auto">
<gl-link :href="installDocsUrl" target="_blank" data-testid="install-docs-link">
<gl-link :href="$options.installDocsUrl" target="_blank" data-testid="install-docs-link">
{{ $options.i18n.learnMoreText }}
</gl-link>
</p>
......@@ -75,7 +77,7 @@ export default {
<gl-button
category="primary"
variant="info"
:href="getStartedDocsUrl"
:href="$options.getStartedDocsUrl"
target="_blank"
class="gl-ml-0!"
>
......
<script>
import {
GlButton,
GlLink,
GlModalDirective,
GlTable,
......@@ -12,11 +11,12 @@ import {
import { s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES, TROUBLESHOOTING_LINK } from '../constants';
import { helpPagePath } from '~/helpers/help_page_helper';
import { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES } from '../constants';
import { getAgentConfigPath } from '../clusters_util';
export default {
components: {
GlButton,
GlLink,
GlTable,
GlIcon,
......@@ -29,10 +29,12 @@ export default {
GlModalDirective,
},
mixins: [timeagoMixin],
inject: ['integrationDocsUrl'],
INSTALL_AGENT_MODAL_ID,
AGENT_STATUSES,
TROUBLESHOOTING_LINK,
troubleshooting_link: helpPagePath('user/clusters/agent/index', {
anchor: 'troubleshooting',
}),
props: {
agents: {
required: true,
......@@ -41,46 +43,48 @@ export default {
},
computed: {
fields() {
const tdClass = 'gl-py-5!';
return [
{
key: 'name',
label: s__('ClusterAgents|Name'),
tdClass,
},
{
key: 'status',
label: s__('ClusterAgents|Connection status'),
tdClass,
},
{
key: 'lastContact',
label: s__('ClusterAgents|Last contact'),
tdClass,
},
{
key: 'configuration',
label: s__('ClusterAgents|Configuration'),
tdClass,
},
];
},
},
methods: {
getCellId(item) {
return `connection-status-${item.name}`;
},
getAgentConfigPath,
},
};
</script>
<template>
<div>
<div class="gl-display-block gl-text-right gl-my-3">
<gl-button
v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
variant="confirm"
category="primary"
>{{ s__('ClusterAgents|Install a new GitLab Agent') }}
</gl-button>
</div>
<gl-table
:items="agents"
:fields="fields"
stacked="md"
head-variant="white"
thead-class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100"
thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
class="gl-mb-4!"
data-testid="cluster-agent-list-table"
>
<template #cell(name)="{ item }">
......@@ -90,27 +94,19 @@ export default {
</template>
<template #cell(status)="{ item }">
<span
:id="`connection-status-${item.name}`"
class="gl-pr-5"
data-testid="cluster-agent-connection-status"
>
<span :id="getCellId(item)" class="gl-md-pr-5" data-testid="cluster-agent-connection-status">
<span :class="$options.AGENT_STATUSES[item.status].class" class="gl-mr-3">
<gl-icon :name="$options.AGENT_STATUSES[item.status].icon" :size="12" /></span
>{{ $options.AGENT_STATUSES[item.status].name }}
</span>
<gl-tooltip
v-if="item.status === 'active'"
:target="`connection-status-${item.name}`"
placement="right"
>
<gl-tooltip v-if="item.status === 'active'" :target="getCellId(item)" placement="right">
<gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.title"
><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template>
</gl-sprintf>
</gl-tooltip>
<gl-popover
v-else
:target="`connection-status-${item.name}`"
:target="getCellId(item)"
:title="$options.AGENT_STATUSES[item.status].tooltip.title"
placement="right"
container="viewport"
......@@ -121,9 +117,8 @@ export default {
>
</p>
<p class="gl-mb-0">
{{ s__('ClusterAgents|For more troubleshooting information go to') }}
<gl-link :href="$options.TROUBLESHOOTING_LINK" target="_blank" class="gl-font-sm">
{{ $options.TROUBLESHOOTING_LINK }}</gl-link
<gl-link :href="$options.troubleshooting_link" target="_blank" class="gl-font-sm">
{{ s__('ClusterAgents|Learn how to troubleshoot') }}</gl-link
>
</p>
</gl-popover>
......@@ -138,15 +133,12 @@ export default {
<template #cell(configuration)="{ item }">
<span data-testid="cluster-agent-configuration-link">
<!-- eslint-disable @gitlab/vue-require-i18n-strings -->
<gl-link v-if="item.configFolder" :href="item.configFolder.webPath">
.gitlab/agents/{{ item.name }}
{{ getAgentConfigPath(item.name) }}
</gl-link>
<span v-else>.gitlab/agents/{{ item.name }}</span>
<!-- eslint-enable @gitlab/vue-require-i18n-strings -->
<span v-else>{{ getAgentConfigPath(item.name) }}</span>
</span>
</template>
</gl-table>
</div>
</template>
......@@ -4,7 +4,6 @@ import { MAX_LIST_COUNT, ACTIVE_CONNECTION_TIME } from '../constants';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import AgentEmptyState from './agent_empty_state.vue';
import AgentTable from './agent_table.vue';
import InstallAgentModal from './install_agent_modal.vue';
export default {
apollo: {
......@@ -26,7 +25,6 @@ export default {
components: {
AgentEmptyState,
AgentTable,
InstallAgentModal,
GlAlert,
GlKeysetPagination,
GlLoadingIcon,
......@@ -135,19 +133,18 @@ export default {
</script>
<template>
<gl-loading-icon v-if="isLoading" size="md" class="gl-mt-3" />
<gl-loading-icon v-if="isLoading" size="md" />
<section v-else-if="agentList" class="gl-mt-3">
<section v-else-if="agentList">
<div v-if="agentList.length">
<AgentTable :agents="agentList" />
<agent-table :agents="agentList" />
<div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination v-bind="agentPageInfo" @prev="prevPage" @next="nextPage" />
</div>
</div>
<AgentEmptyState v-else :has-configurations="hasConfigurations" />
<InstallAgentModal @agentRegistered="reloadAgents" />
<agent-empty-state v-else :has-configurations="hasConfigurations" />
</section>
<gl-alert v-else variant="danger" :dismissible="false">
......
......@@ -57,30 +57,37 @@ export default {
},
},
fields() {
const tdClass = 'gl-py-5!';
return [
{
key: 'name',
label: __('Kubernetes cluster'),
tdClass,
},
{
key: 'environment_scope',
label: __('Environment scope'),
tdClass,
},
{
key: 'node_size',
label: __('Nodes'),
tdClass,
},
{
key: 'total_cpu',
label: __('Total cores (CPUs)'),
tdClass,
},
{
key: 'total_memory',
label: __('Total memory (GB)'),
tdClass,
},
{
key: 'cluster_type',
label: __('Cluster level'),
tdClass,
formatter: (value) => CLUSTER_TYPES[value],
},
];
......@@ -201,7 +208,7 @@ export default {
</script>
<template>
<gl-loading-icon v-if="loadingClusters" size="md" class="gl-mt-3" />
<gl-loading-icon v-if="loadingClusters" size="md" />
<section v-else>
<ancestor-notice />
......@@ -210,10 +217,11 @@ export default {
v-if="hasClusters"
:items="clusters"
:fields="fields"
fixed
stacked="md"
head-variant="white"
thead-class="gl-border-b-solid gl-border-b-1 gl-border-b-gray-100"
class="qa-clusters-table"
thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
class="qa-clusters-table gl-mb-4!"
data-testid="cluster_list_table"
>
<template #cell(name)="{ item }">
......
<script>
import { GlDropdown, GlDropdownItem, GlModalDirective } from '@gitlab/ui';
import { INSTALL_AGENT_MODAL_ID, CLUSTERS_ACTIONS } from '../constants';
export default {
i18n: CLUSTERS_ACTIONS,
INSTALL_AGENT_MODAL_ID,
components: {
GlDropdown,
GlDropdownItem,
},
directives: {
GlModalDirective,
},
inject: ['newClusterPath', 'addClusterPath'],
};
</script>
<template>
<div class="nav-controls gl-ml-auto">
<gl-dropdown
ref="dropdown"
v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
category="primary"
variant="confirm"
:text="$options.i18n.actionsButton"
split
right
>
<gl-dropdown-item :href="newClusterPath" data-testid="new-cluster-link" @click.stop>
{{ $options.i18n.createNewCluster }}
</gl-dropdown-item>
<gl-dropdown-item
v-gl-modal-directive="$options.INSTALL_AGENT_MODAL_ID"
data-testid="connect-new-agent-link"
>
{{ $options.i18n.connectWithAgent }}
</gl-dropdown-item>
<gl-dropdown-item :href="addClusterPath" data-testid="connect-cluster-link" @click.stop>
{{ $options.i18n.connectExistingCluster }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</template>
......@@ -14,6 +14,7 @@ export default {
},
inject: ['emptyStateHelpText', 'clustersEmptyStateImage', 'newClusterPath'],
learnMoreHelpUrl: helpPagePath('user/project/clusters/index'),
multipleClustersHelpUrl: helpPagePath('user/project/clusters/multiple_kubernetes_clusters'),
computed: {
...mapState(['canAddCluster']),
},
......
<script>
import { GlTabs, GlTab } from '@gitlab/ui';
import { CLUSTERS_TABS } 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';
export default {
components: {
GlTabs,
GlTab,
ClustersActions,
Clusters,
Agents,
InstallAgentModal,
},
CLUSTERS_TABS,
props: {
defaultBranchName: {
default: '.noBranch',
required: false,
type: String,
},
},
data() {
return {
selectedTabIndex: 0,
};
},
methods: {
onTabChange(tabName) {
this.selectedTabIndex = CLUSTERS_TABS.findIndex((tab) => tab.queryParamValue === tabName);
},
},
};
</script>
<template>
<div>
<gl-tabs
v-model="selectedTabIndex"
sync-active-tab-with-query-params
nav-class="gl-flex-grow-1 gl-align-items-center"
lazy
>
<gl-tab
v-for="(tab, idx) in $options.CLUSTERS_TABS"
:key="idx"
:title="tab.title"
:query-param-value="tab.queryParamValue"
class="gl-line-height-20 gl-mt-5"
>
<component
:is="tab.component"
:default-branch-name="defaultBranchName"
data-testid="clusters-tab-component"
@changeTab="onTabChange"
/>
</gl-tab>
<template #tabs-end>
<clusters-actions />
</template>
</gl-tabs>
<install-agent-modal :default-branch-name="defaultBranchName" />
</div>
</template>
......@@ -12,9 +12,11 @@ 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 } from '../constants';
import { INSTALL_AGENT_MODAL_ID, I18N_INSTALL_AGENT_MODAL, MAX_LIST_COUNT } 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';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import AvailableAgentsDropdown from './available_agents_dropdown.vue';
export default {
......@@ -33,12 +35,20 @@ export default {
GlSprintf,
},
inject: ['projectPath', 'kasAddress'],
props: {
defaultBranchName: {
default: '.noBranch',
required: false,
type: String,
},
},
data() {
return {
registering: false,
agentName: null,
agentToken: null,
error: null,
clusterAgent: null,
};
},
computed: {
......@@ -62,20 +72,24 @@ export default {
advancedInstallPath() {
return helpPagePath('user/clusters/agent/install/index', { anchor: 'advanced-installation' });
},
getAgentsQueryVariables() {
return {
defaultBranchName: this.defaultBranchName,
first: MAX_LIST_COUNT,
last: null,
projectPath: this.projectPath,
};
},
},
methods: {
setAgentName(name) {
this.agentName = name;
},
cancelClicked() {
this.$refs.modal.hide();
},
doneClicked() {
this.$emit('agentRegistered');
closeModal() {
this.$refs.modal.hide();
},
resetModal() {
this.registering = null;
this.registering = false;
this.agentName = null;
this.agentToken = null;
this.error = null;
......@@ -90,6 +104,14 @@ export default {
projectPath: this.projectPath,
},
},
update: (store, { data: { createClusterAgent } }) => {
addAgentToStore(
store,
createClusterAgent,
getAgentsQuery,
this.getAgentsQueryVariables,
);
},
})
.then(({ data: { createClusterAgent } }) => createClusterAgent);
},
......@@ -117,6 +139,8 @@ export default {
throw new Error(agentErrors[0]);
}
this.clusterAgent = clusterAgent;
const { errors: tokenErrors, secret } = await this.createAgentTokenMutation(
clusterAgent.id,
);
......@@ -240,10 +264,10 @@ export default {
</template>
<template #modal-footer>
<gl-button v-if="canCancel" @click="cancelClicked">{{ $options.i18n.cancel }} </gl-button>
<gl-button v-if="canCancel" @click="closeModal">{{ $options.i18n.cancel }} </gl-button>
<gl-button v-if="registered" variant="confirm" category="primary" @click="doneClicked"
>{{ $options.i18n.done }}
<gl-button v-if="registered" variant="confirm" category="primary" @click="closeModal"
>{{ $options.i18n.close }}
</gl-button>
<gl-button
......@@ -252,7 +276,7 @@ export default {
variant="confirm"
category="primary"
@click="registerAgent"
>{{ $options.i18n.next }}
>{{ $options.i18n.registerAgentButton }}
</gl-button>
</template>
</gl-modal>
......
......@@ -3,8 +3,6 @@ import { __, s__, sprintf } from '~/locale';
export const MAX_LIST_COUNT = 25;
export const INSTALL_AGENT_MODAL_ID = 'install-agent';
export const ACTIVE_CONNECTION_TIME = 480000;
export const TROUBLESHOOTING_LINK =
'https://docs.gitlab.com/ee/user/clusters/agent/#troubleshooting';
export const CLUSTER_ERRORS = {
default: {
......@@ -66,8 +64,8 @@ export const STATUSES = {
};
export const I18N_INSTALL_AGENT_MODAL = {
next: __('Next'),
done: __('Done'),
registerAgentButton: s__('ClusterAgents|Register Agent'),
close: __('Close'),
cancel: __('Cancel'),
modalTitle: s__('ClusterAgents|Install new Agent'),
......@@ -168,3 +166,23 @@ export const I18N_CLUSTERS_EMPTY_STATE = {
learnMoreLinkText: s__('ClusterIntegration|Learn more about the GitLab managed clusters'),
buttonText: s__('ClusterIntegration|Connect with a certificate'),
};
export const CLUSTERS_TABS = [
{
title: s__('ClusterAgents|Agent'),
component: 'agents',
queryParamValue: 'agent',
},
{
title: s__('ClusterAgents|Certificate based'),
component: 'clusters',
queryParamValue: 'certificate_based',
},
];
export const CLUSTERS_ACTIONS = {
actionsButton: s__('ClusterAgents|Actions'),
createNewCluster: s__('ClusterAgents|Create new cluster'),
connectWithAgent: s__('ClusterAgents|Connect with Agent'),
connectExistingCluster: s__('ClusterAgents|Connect with certificate'),
};
import produce from 'immer';
import { getAgentConfigPath } from '../clusters_util';
export function addAgentToStore(store, createClusterAgent, query, variables) {
const { clusterAgent } = createClusterAgent;
const sourceData = store.readQuery({
query,
variables,
});
const data = produce(sourceData, (draftData) => {
const configuration = {
name: clusterAgent.name,
path: getAgentConfigPath(clusterAgent.name),
webPath: clusterAgent.webPath,
__typename: 'TreeEntry',
};
draftData.project.clusterAgents.nodes.push(clusterAgent);
draftData.project.repository.tree.trees.nodes.push(configuration);
});
store.writeQuery({
query,
variables,
data,
});
}
fragment ClusterAgentFragment on ClusterAgent {
id
name
webPath
tokens {
nodes {
lastUsedAt
}
}
}
#import "../fragments/cluster_agent.fragment.graphql"
mutation createClusterAgent($input: CreateClusterAgentInput!) {
createClusterAgent(input: $input) {
clusterAgent {
id
...ClusterAgentFragment
}
errors
}
......
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
#import "../fragments/cluster_agent.fragment.graphql"
query getAgents(
$defaultBranchName: String!
......@@ -13,14 +14,7 @@ query getAgents(
project(fullPath: $projectPath) {
clusterAgents(first: $first, last: $last, before: $beforeAgent, after: $afterAgent) {
nodes {
id
name
webPath
tokens {
nodes {
lastUsedAt
}
}
...ClusterAgentFragment
}
pageInfo {
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import loadClusters from './load_clusters';
import loadAgents from './load_agents';
import loadMainView from './load_main_view';
Vue.use(VueApollo);
export default () => {
loadClusters(Vue);
loadAgents(Vue, VueApollo);
loadMainView(Vue, VueApollo);
};
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import Agents from './components/agents.vue';
import ClustersMainView from './components/clusters_main_view.vue';
import { createStore } from './store';
export default (Vue, VueApollo) => {
const el = document.querySelector('#js-cluster-agents-list');
Vue.use(VueApollo);
export default () => {
const el = document.querySelector('.js-clusters-main-view');
if (!el) {
return null;
......@@ -14,11 +19,11 @@ export default (Vue, VueApollo) => {
emptyStateImage,
defaultBranchName,
projectPath,
multipleClustersDocsUrl,
installDocsUrl,
getStartedDocsUrl,
integrationDocsUrl,
kasAddress,
newClusterPath,
addClusterPath,
emptyStateHelpText,
clustersEmptyStateImage,
} = el.dataset;
return new Vue({
......@@ -27,14 +32,15 @@ export default (Vue, VueApollo) => {
provide: {
emptyStateImage,
projectPath,
multipleClustersDocsUrl,
installDocsUrl,
getStartedDocsUrl,
integrationDocsUrl,
kasAddress,
newClusterPath,
addClusterPath,
emptyStateHelpText,
clustersEmptyStateImage,
},
store: createStore(el.dataset),
render(createElement) {
return createElement(Agents, {
return createElement(ClustersMainView, {
props: {
defaultBranchName,
},
......
......@@ -20,4 +20,23 @@
@include gl-flex-wrap;
}
}
.gl-card-body {
@include media-breakpoint-up(sm) {
@include gl-pt-2;
min-height: 372px;
}
}
@include media-breakpoint-down(xs) {
.nav-controls {
@include gl-w-full;
order: -1;
.gl-new-dropdown,
.split-content-button {
@include gl-w-full;
}
}
}
}
......@@ -16,19 +16,6 @@ module ClustersHelper
clusterable.is_a?(Project)
end
def js_cluster_agents_list_data(clusterable_project)
{
default_branch_name: clusterable_project.default_branch,
empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg'),
project_path: clusterable_project.full_path,
multiple_clusters_docs_url: help_page_path('user/project/clusters/multiple_kubernetes_clusters'),
install_docs_url: help_page_path('administration/clusters/kas'),
get_started_docs_url: help_page_path('user/clusters/agent/index', anchor: 'define-a-configuration-repository'),
integration_docs_url: help_page_path('user/clusters/agent/index', anchor: 'get-started-with-gitops-and-the-gitlab-agent'),
kas_address: Gitlab::Kas.external_url
}
end
def js_clusters_list_data(clusterable)
{
ancestor_help_path: help_page_path('user/group/clusters/index', anchor: 'cluster-precedence'),
......@@ -45,6 +32,16 @@ module ClustersHelper
}
end
def js_clusters_data(clusterable)
{
default_branch_name: clusterable.default_branch,
empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg'),
project_path: clusterable.full_path,
add_cluster_path: clusterable.new_path(tab: 'add'),
kas_address: Gitlab::Kas.external_url
}.merge(js_clusters_list_data(clusterable))
end
def js_cluster_form_data(cluster, can_edit)
{
enabled: cluster.enabled?.to_s,
......
......@@ -3,24 +3,10 @@
= render_gcp_signup_offer
.clusters-container.gl-my-2
.clusters-container
- if display_cluster_agents?(clusterable)
.js-toggle-container
%ul.nav-links.nav-tabs.nav{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
%a.nav-link.active{ href: "#certificate-clusters-pane", id: "certificate-clusters-tab", data: { toggle: 'tab' }, role: 'tab' }
%span= s_('ClusterIntegration|Clusters connected with a certificate')
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: "#agent-clusters-pane", id: "agent-clusters-tab", data: { toggle: 'tab' }, role: 'tab' }
%span= s_('ClusterIntegration|GitLab Agent managed clusters')
.tab-content
.tab-pane.active{ id: 'certificate-clusters-pane', role: 'tabpanel' }
= render 'cluster_list', clusters: @clusters
.tab-pane{ id: 'agent-clusters-pane', role: 'tabpanel' }
#js-cluster-agents-list{ data: js_cluster_agents_list_data(clusterable) }
.gl-my-6
.js-clusters-main-view{ data: js_clusters_data(clusterable) }
- else
= render 'cluster_list', clusters: @clusters
......@@ -7321,6 +7321,12 @@ msgstr ""
msgid "ClusterAgents|Access tokens"
msgstr ""
msgid "ClusterAgents|Actions"
msgstr ""
msgid "ClusterAgents|Agent"
msgstr ""
msgid "ClusterAgents|Agent might not be connected to GitLab"
msgstr ""
......@@ -7339,12 +7345,21 @@ msgstr ""
msgid "ClusterAgents|An unknown error occurred. Please try again."
msgstr ""
msgid "ClusterAgents|Certificate based"
msgstr ""
msgid "ClusterAgents|Configuration"
msgstr ""
msgid "ClusterAgents|Connect with Agent"
msgstr ""
msgid "ClusterAgents|Connect with a GitLab Agent"
msgstr ""
msgid "ClusterAgents|Connect with certificate"
msgstr ""
msgid "ClusterAgents|Connected"
msgstr ""
......@@ -7354,6 +7369,9 @@ msgstr ""
msgid "ClusterAgents|Copy token"
msgstr ""
msgid "ClusterAgents|Create new cluster"
msgstr ""
msgid "ClusterAgents|Created by"
msgstr ""
......@@ -7369,18 +7387,12 @@ msgstr ""
msgid "ClusterAgents|For alternative installation methods %{linkStart}go to the documentation%{linkEnd}."
msgstr ""
msgid "ClusterAgents|For more troubleshooting information go to"
msgstr ""
msgid "ClusterAgents|Go to the repository"
msgstr ""
msgid "ClusterAgents|If you are setting up multiple clusters and are using Auto DevOps, %{linkStart}read about using multiple Kubernetes clusters first.%{linkEnd}"
msgstr ""
msgid "ClusterAgents|Install a new GitLab Agent"
msgstr ""
msgid "ClusterAgents|Install new Agent"
msgstr ""
......@@ -7393,6 +7405,9 @@ msgstr ""
msgid "ClusterAgents|Learn how to create an agent access token"
msgstr ""
msgid "ClusterAgents|Learn how to troubleshoot"
msgstr ""
msgid "ClusterAgents|Learn more about the GitLab Kubernetes Agent."
msgstr ""
......@@ -7417,6 +7432,9 @@ msgstr ""
msgid "ClusterAgents|Recommended installation method"
msgstr ""
msgid "ClusterAgents|Register Agent"
msgstr ""
msgid "ClusterAgents|Registering Agent"
msgstr ""
......@@ -7603,9 +7621,6 @@ msgstr ""
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "ClusterIntegration|Clusters connected with a certificate"
msgstr ""
msgid "ClusterIntegration|Connect cluster with certificate"
msgstr ""
......@@ -7741,9 +7756,6 @@ msgstr ""
msgid "ClusterIntegration|Fetching zones"
msgstr ""
msgid "ClusterIntegration|GitLab Agent managed clusters"
msgstr ""
msgid "ClusterIntegration|GitLab Integration"
msgstr ""
......
......@@ -24,21 +24,21 @@ RSpec.describe 'Cluster agent registration', :js do
end
it 'allows the user to select an agent to install, and displays the resulting agent token' do
click_link('GitLab Agent managed clusters')
click_button('Connect with a GitLab Agent')
click_button('Actions')
expect(page).to have_content('Install new Agent')
click_button('Select an Agent')
click_button('example-agent-2')
click_button('Next')
click_button('Register Agent')
expect(page).to have_content('The token value will not be shown again after you close this window.')
expect(page).to have_content('example-agent-token')
expect(page).to have_content('docker run --pull=always --rm')
click_button('Done')
within find('.modal-footer') do
click_button('Close')
end
expect(page).to have_link('example-agent-2')
expect(page).to have_no_content('Install new Agent')
end
end
......@@ -22,8 +22,6 @@ RSpec.describe 'ClusterAgents', :js do
end
it 'displays empty state', :aggregate_failures do
click_link 'GitLab Agent managed clusters'
expect(page).to have_content('Connect with a GitLab Agent')
expect(page).to have_selector('.empty-state')
end
......@@ -36,8 +34,6 @@ RSpec.describe 'ClusterAgents', :js do
end
it 'displays a table with agent', :aggregate_failures do
click_link 'GitLab Agent managed clusters'
expect(page).to have_content(agent.name)
expect(page).to have_selector('[data-testid="cluster-agent-list-table"] tbody tr', count: 1)
end
......
......@@ -19,6 +19,7 @@ RSpec.describe 'AWS EKS Cluster', :js do
before do
visit project_clusters_path(project)
click_link 'Certificate based'
click_link 'Connect with a certificate'
end
......
......@@ -33,6 +33,7 @@ RSpec.describe 'Gcp Cluster', :js do
before do
visit project_clusters_path(project)
click_link 'Certificate based'
click_link 'Connect with a certificate'
click_link 'Create new cluster'
click_link 'Google GKE'
......@@ -143,8 +144,9 @@ RSpec.describe 'Gcp Cluster', :js do
before do
visit project_clusters_path(project)
click_link 'Connect cluster with certificate'
click_link 'Connect existing cluster'
click_link 'Certificate based'
click_button(class: 'dropdown-toggle-split')
click_link 'Connect with certificate'
end
it 'user sees the "Environment scope" field' do
......@@ -158,6 +160,7 @@ RSpec.describe 'Gcp Cluster', :js do
click_button 'Remove integration and resources'
fill_in 'confirm_cluster_name_input', with: cluster.name
click_button 'Remove integration'
click_link 'Certificate based'
end
it 'user sees creation form with the successful message' do
......@@ -171,6 +174,7 @@ RSpec.describe 'Gcp Cluster', :js do
context 'when user has not dismissed GCP signup offer' do
before do
visit project_clusters_path(project)
click_link 'Certificate based'
end
it 'user sees offer on cluster index page' do
......@@ -187,6 +191,7 @@ RSpec.describe 'Gcp Cluster', :js do
context 'when user has dismissed GCP signup offer' do
before do
visit project_clusters_path(project)
click_link 'Certificate based'
end
it 'user does not see offer after dismissing' do
......@@ -202,12 +207,11 @@ RSpec.describe 'Gcp Cluster', :js do
end
context 'when third party offers are disabled', :clean_gitlab_redis_shared_state do
let(:admin) { create(:admin) }
let(:user) { create(:admin) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
gitlab_enable_admin_mode_sign_in(user)
visit general_admin_application_settings_path
end
......
......@@ -25,6 +25,7 @@ RSpec.describe 'User Cluster', :js do
before do
visit project_clusters_path(project)
click_link 'Certificate based'
click_link 'Connect with a certificate'
click_link 'Connect existing cluster'
end
......@@ -112,6 +113,7 @@ RSpec.describe 'User Cluster', :js do
click_button 'Remove integration and resources'
fill_in 'confirm_cluster_name_input', with: cluster.name
click_button 'Remove integration'
click_link 'Certificate based'
end
it 'user sees creation form with the successful message' do
......
......@@ -16,6 +16,7 @@ RSpec.describe 'Clusters', :js do
context 'when user does not have a cluster and visits cluster index page' do
before do
visit project_clusters_path(project)
click_link 'Certificate based'
end
it 'sees empty state' do
......@@ -33,16 +34,17 @@ RSpec.describe 'Clusters', :js do
before do
create(:cluster, :provided_by_user, name: 'default-cluster', environment_scope: '*', projects: [project])
visit project_clusters_path(project)
click_link 'Certificate based'
click_button(class: 'dropdown-toggle-split')
end
it 'user sees an add cluster button' do
expect(page).to have_selector('.js-add-cluster:not(.readonly)')
expect(page).to have_content('Connect with certificate')
end
context 'when user filled form with environment scope' do
before do
click_link 'Connect cluster with certificate'
click_link 'Connect existing cluster'
click_link 'Connect with certificate'
fill_in 'cluster_name', with: 'staging-cluster'
fill_in 'cluster_environment_scope', with: 'staging/*'
click_button 'Add Kubernetes cluster'
......@@ -70,8 +72,7 @@ RSpec.describe 'Clusters', :js do
context 'when user updates duplicated environment scope' do
before do
click_link 'Connect cluster with certificate'
click_link 'Connect existing cluster'
click_link 'Connect with certificate'
fill_in 'cluster_name', with: 'staging-cluster'
fill_in 'cluster_environment_scope', with: '*'
fill_in 'cluster_platform_kubernetes_attributes_api_url', with: 'https://0.0.0.0'
......@@ -108,15 +109,12 @@ RSpec.describe 'Clusters', :js do
create(:cluster, :provided_by_gcp, name: 'default-cluster', environment_scope: '*', projects: [project])
visit project_clusters_path(project)
end
it 'user sees a add cluster button' do
expect(page).to have_selector('.js-add-cluster:not(.readonly)')
click_link 'Certificate based'
end
context 'when user filled form with environment scope' do
before do
click_link 'Connect cluster with certificate'
click_button(class: 'dropdown-toggle-split')
click_link 'Create new cluster'
click_link 'Google GKE'
......@@ -161,7 +159,7 @@ RSpec.describe 'Clusters', :js do
context 'when user updates duplicated environment scope' do
before do
click_link 'Connect cluster with certificate'
click_button(class: 'dropdown-toggle-split')
click_link 'Create new cluster'
click_link 'Google GKE'
......@@ -192,6 +190,7 @@ RSpec.describe 'Clusters', :js do
before do
visit project_clusters_path(project)
click_link 'Certificate based'
end
it 'user sees a table with one cluster' do
......@@ -214,6 +213,7 @@ RSpec.describe 'Clusters', :js do
before do
visit project_clusters_path(project)
click_link 'Certificate based'
click_link 'Connect with a certificate'
click_link 'Create new cluster'
end
......
import { GlAlert, GlEmptyState, GlSprintf } from '@gitlab/ui';
import AgentEmptyState from '~/clusters_list/components/agent_empty_state.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { helpPagePath } from '~/helpers/help_page_helper';
const emptyStateImage = '/path/to/image';
const projectPath = 'path/to/project';
const multipleClustersDocsUrl = 'path/to/multipleClustersDocs';
const installDocsUrl = 'path/to/installDocs';
const getStartedDocsUrl = 'path/to/getStartedDocs';
const integrationDocsUrl = 'path/to/integrationDocs';
const multipleClustersDocsUrl = helpPagePath('user/project/clusters/multiple_kubernetes_clusters');
const installDocsUrl = helpPagePath('administration/clusters/kas');
describe('AgentEmptyStateComponent', () => {
let wrapper;
......@@ -18,10 +17,6 @@ describe('AgentEmptyStateComponent', () => {
const provideData = {
emptyStateImage,
projectPath,
multipleClustersDocsUrl,
installDocsUrl,
getStartedDocsUrl,
integrationDocsUrl,
};
const findConfigurationsAlert = () => wrapper.findComponent(GlAlert);
......@@ -41,7 +36,6 @@ describe('AgentEmptyStateComponent', () => {
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
......
import { GlButton, GlLink, GlIcon } from '@gitlab/ui';
import { GlLink, GlIcon } from '@gitlab/ui';
import AgentTable from '~/clusters_list/components/agent_table.vue';
import { ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants';
import { mountExtended } from 'helpers/vue_test_utils_helper';
......@@ -47,7 +47,6 @@ const propsData = {
},
],
};
const provideData = { integrationDocsUrl: 'path/to/integrationDocs' };
describe('AgentTable', () => {
let wrapper;
......@@ -60,7 +59,7 @@ describe('AgentTable', () => {
wrapper.findAllByTestId('cluster-agent-configuration-link').at(at);
beforeEach(() => {
wrapper = mountExtended(AgentTable, { propsData, provide: provideData });
wrapper = mountExtended(AgentTable, { propsData });
});
afterEach(() => {
......@@ -70,10 +69,6 @@ describe('AgentTable', () => {
}
});
it('displays header button', () => {
expect(wrapper.find(GlButton).text()).toBe('Install a new GitLab Agent');
});
describe('agent table', () => {
it.each`
agentName | link | lineNumber
......
......@@ -54,7 +54,6 @@ describe('Agents', () => {
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
......@@ -234,7 +233,11 @@ describe('Agents', () => {
};
beforeEach(() => {
wrapper = shallowMount(Agents, { mocks, propsData, provide: provideData });
wrapper = shallowMount(Agents, {
mocks,
propsData,
provide: provideData,
});
return wrapper.vm.$nextTick();
});
......
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ClustersActions from '~/clusters_list/components/clusters_actions.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { INSTALL_AGENT_MODAL_ID, CLUSTERS_ACTIONS } from '~/clusters_list/constants';
describe('ClustersActionsComponent', () => {
let wrapper;
const newClusterPath = 'path/to/create/cluster';
const addClusterPath = 'path/to/connect/existing/cluster';
const provideData = {
newClusterPath,
addClusterPath,
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findNewClusterLink = () => wrapper.findByTestId('new-cluster-link');
const findConnectClusterLink = () => wrapper.findByTestId('connect-cluster-link');
const findConnectNewAgentLink = () => wrapper.findByTestId('connect-new-agent-link');
beforeEach(() => {
wrapper = shallowMountExtended(ClustersActions, {
provide: provideData,
directives: {
GlModalDirective: createMockDirective(),
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('renders actions menu', () => {
expect(findDropdown().props('text')).toBe(CLUSTERS_ACTIONS.actionsButton);
});
it('renders a dropdown with 3 actions items', () => {
expect(findDropdownItems()).toHaveLength(3);
});
it('renders correct href attributes for the links', () => {
expect(findNewClusterLink().attributes('href')).toBe(newClusterPath);
expect(findConnectClusterLink().attributes('href')).toBe(addClusterPath);
});
it('renders correct modal id for the agent link', () => {
const binding = getBinding(findConnectNewAgentLink().element, 'gl-modal-directive');
expect(binding.value).toBe(INSTALL_AGENT_MODAL_ID);
});
});
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';
const defaultBranchName = 'default-branch';
describe('ClustersMainViewComponent', () => {
let wrapper;
const propsData = {
defaultBranchName,
};
beforeEach(() => {
wrapper = shallowMountExtended(ClustersMainView, {
propsData,
});
});
afterEach(() => {
wrapper.destroy();
});
const findTabs = () => wrapper.findComponent(GlTabs);
const findAllTabs = () => wrapper.findAllComponents(GlTab);
const findGlTabAtIndex = (index) => findAllTabs().at(index);
const findComponent = () => wrapper.findByTestId('clusters-tab-component');
it('renders `GlTabs` with `syncActiveTabWithQueryParams` and `queryParamName` props set', () => {
expect(findTabs().exists()).toBe(true);
expect(findTabs().props('syncActiveTabWithQueryParams')).toBe(true);
});
it('renders correct number of tabs', () => {
expect(findAllTabs()).toHaveLength(CLUSTERS_TABS.length);
});
it('passes child-component param to the component', () => {
expect(findComponent().props('defaultBranchName')).toBe(defaultBranchName);
});
describe('tabs', () => {
it.each`
tabTitle | queryParamValue | lineNumber
${'Agent'} | ${'agent'} | ${0}
${'Certificate based'} | ${'certificate_based'} | ${1}
`(
'renders correct tab title and query param value',
({ tabTitle, queryParamValue, lineNumber }) => {
expect(findGlTabAtIndex(lineNumber).attributes('title')).toBe(tabTitle);
expect(findGlTabAtIndex(lineNumber).props('queryParamValue')).toBe(queryParamValue);
},
);
});
});
......@@ -3,7 +3,8 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import AvailableAgentsDropdown from '~/clusters_list/components/available_agents_dropdown.vue';
import InstallAgentModal from '~/clusters_list/components/install_agent_modal.vue';
import { I18N_INSTALL_AGENT_MODAL } from '~/clusters_list/constants';
import { I18N_INSTALL_AGENT_MODAL, MAX_LIST_COUNT } from '~/clusters_list/constants';
import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.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 createMockApollo from 'helpers/mock_apollo_helper';
......@@ -14,12 +15,16 @@ import {
createAgentErrorResponse,
createAgentTokenResponse,
createAgentTokenErrorResponse,
getAgentResponse,
} from '../mocks/apollo';
import ModalStub from '../stubs';
const localVue = createLocalVue();
localVue.use(VueApollo);
const projectPath = 'path/to/project';
const defaultBranchName = 'default';
describe('InstallAgentModal', () => {
let wrapper;
let apolloProvider;
......@@ -45,10 +50,14 @@ describe('InstallAgentModal', () => {
const createWrapper = () => {
const provide = {
projectPath: 'path/to/project',
projectPath,
kasAddress: 'kas.example.com',
};
const propsData = {
defaultBranchName,
};
wrapper = shallowMount(InstallAgentModal, {
attachTo: document.body,
stubs: {
......@@ -57,11 +66,26 @@ describe('InstallAgentModal', () => {
localVue,
apolloProvider,
provide,
propsData,
});
};
const writeQuery = () => {
apolloProvider.clients.defaultClient.cache.writeQuery({
query: getAgentsQuery,
variables: {
projectPath,
defaultBranchName,
first: MAX_LIST_COUNT,
last: null,
},
data: getAgentResponse.data,
});
};
const mockSelectedAgentResponse = () => {
createWrapper();
writeQuery();
wrapper.vm.setAgentName('agent-name');
findActionButton().vm.$emit('click');
......@@ -95,7 +119,7 @@ describe('InstallAgentModal', () => {
it('renders a disabled next button', () => {
expect(findActionButton().isVisible()).toBe(true);
expect(findActionButton().text()).toBe(i18n.next);
expect(findActionButton().text()).toBe(i18n.registerAgentButton);
expectDisabledAttribute(findActionButton(), true);
});
});
......@@ -126,7 +150,7 @@ describe('InstallAgentModal', () => {
it('creates an agent and token', () => {
expect(createAgentHandler).toHaveBeenCalledWith({
input: { name: 'agent-name', projectPath: 'path/to/project' },
input: { name: 'agent-name', projectPath },
});
expect(createAgentTokenHandler).toHaveBeenCalledWith({
......@@ -134,9 +158,9 @@ describe('InstallAgentModal', () => {
});
});
it('renders a done button', () => {
it('renders a close button', () => {
expect(findActionButton().isVisible()).toBe(true);
expect(findActionButton().text()).toBe(i18n.done);
expect(findActionButton().text()).toBe(i18n.close);
expectDisabledAttribute(findActionButton(), false);
});
......
const agent = {
id: 'agent-id',
name: 'agent-name',
webPath: 'agent-webPath',
};
const token = {
id: 'token-id',
lastUsedAt: null,
};
const tokens = {
nodes: [token],
};
const pageInfo = {
endCursor: '',
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
};
export const createAgentResponse = {
data: {
createClusterAgent: {
clusterAgent: {
id: 'agent-id',
...agent,
tokens,
},
errors: [],
},
......@@ -13,7 +33,8 @@ export const createAgentErrorResponse = {
data: {
createClusterAgent: {
clusterAgent: {
id: 'agent-id',
...agent,
tokens,
},
errors: ['could not create agent'],
},
......@@ -23,9 +44,7 @@ export const createAgentErrorResponse = {
export const createAgentTokenResponse = {
data: {
clusterAgentTokenCreate: {
token: {
id: 'token-id',
},
token,
secret: 'mock-agent-token',
errors: [],
},
......@@ -35,11 +54,22 @@ export const createAgentTokenResponse = {
export const createAgentTokenErrorResponse = {
data: {
clusterAgentTokenCreate: {
token: {
id: 'token-id',
},
token,
secret: 'mock-agent-token',
errors: ['could not create agent token'],
},
},
};
export const getAgentResponse = {
data: {
project: {
clusterAgents: { nodes: [{ ...agent, tokens }], pageInfo },
repository: {
tree: {
trees: { nodes: [{ ...agent, path: null }], pageInfo },
},
},
},
},
};
......@@ -59,35 +59,6 @@ RSpec.describe ClustersHelper do
end
end
describe '#js_cluster_agents_list_data' do
let_it_be(:project) { build(:project, :repository) }
subject { helper.js_cluster_agents_list_data(project) }
it 'displays project default branch' do
expect(subject[:default_branch_name]).to eq(project.default_branch)
end
it 'displays image path' do
expect(subject[:empty_state_image]).to match(%r(/illustrations/empty-state/empty-state-agents|svg))
end
it 'displays project path' do
expect(subject[:project_path]).to eq(project.full_path)
end
it 'generates docs urls' do
expect(subject[:multiple_clusters_docs_url]).to eq(help_page_path('user/project/clusters/multiple_kubernetes_clusters'))
expect(subject[:install_docs_url]).to eq(help_page_path('administration/clusters/kas'))
expect(subject[:get_started_docs_url]).to eq(help_page_path('user/clusters/agent/index', anchor: 'define-a-configuration-repository'))
expect(subject[:integration_docs_url]).to eq(help_page_path('user/clusters/agent/index', anchor: 'get-started-with-gitops-and-the-gitlab-agent'))
end
it 'displays kas address' do
expect(subject[:kas_address]).to eq(Gitlab::Kas.external_url)
end
end
describe '#js_clusters_list_data' do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { build(:project) }
......@@ -153,6 +124,34 @@ RSpec.describe ClustersHelper do
end
end
describe '#js_clusters_data' do
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { build(:project) }
let_it_be(:clusterable) { ClusterablePresenter.fabricate(project, current_user: current_user) }
subject { helper.js_clusters_data(clusterable) }
it 'displays project default branch' do
expect(subject[:default_branch_name]).to eq(project.default_branch)
end
it 'displays image path' do
expect(subject[:empty_state_image]).to match(%r(/illustrations/empty-state/empty-state-agents|svg))
end
it 'displays project path' do
expect(subject[:project_path]).to eq(project.full_path)
end
it 'displays add cluster using certificate path' do
expect(subject[:add_cluster_path]).to eq("#{project_path(project)}/-/clusters/new?tab=add")
end
it 'displays kas address' do
expect(subject[:kas_address]).to eq(Gitlab::Kas.external_url)
end
end
describe '#js_cluster_new' do
subject { helper.js_cluster_new }
......
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