Commit 7c33b405 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch 'mg-add-knative-domain-dropdown' into 'master'

Allow users to assign a serverless domain to Knative (frontend)

See merge request gitlab-org/gitlab!28431
parents 921ccfd8 9e568113
......@@ -259,7 +259,7 @@ export default class Clusters {
eventHub.$on('installApplication', this.installApplication);
eventHub.$on('updateApplication', data => this.updateApplication(data));
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
eventHub.$on('setKnativeHostname', data => this.setKnativeHostname(data));
eventHub.$on('setKnativeDomain', data => this.setKnativeDomain(data));
eventHub.$on('uninstallApplication', data => this.uninstallApplication(data));
eventHub.$on('setCrossplaneProviderStack', data => this.setCrossplaneProviderStack(data));
eventHub.$on('setIngressModSecurityEnabled', data => this.setIngressModSecurityEnabled(data));
......@@ -275,7 +275,7 @@ export default class Clusters {
eventHub.$off('installApplication', this.installApplication);
eventHub.$off('updateApplication', this.updateApplication);
eventHub.$off('saveKnativeDomain');
eventHub.$off('setKnativeHostname');
eventHub.$off('setKnativeDomain');
eventHub.$off('setCrossplaneProviderStack');
eventHub.$off('uninstallApplication');
eventHub.$off('setIngressModSecurityEnabled');
......@@ -521,10 +521,10 @@ export default class Clusters {
});
}
setKnativeHostname(data) {
const appId = data.id;
this.store.updateAppProperty(appId, 'isEditingHostName', true);
this.store.updateAppProperty(appId, 'hostname', data.hostname);
setKnativeDomain({ id: appId, domain, domainId }) {
this.store.updateAppProperty(appId, 'isEditingDomain', true);
this.store.updateAppProperty(appId, 'hostname', domain);
this.store.updateAppProperty(appId, 'pagesDomain', domainId ? { id: domainId, domain } : null);
}
setCrossplaneProviderStack(data) {
......
......@@ -240,16 +240,20 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
this.helmInstallIllustration = helmInstallIllustration;
},
methods: {
saveKnativeDomain(hostname) {
saveKnativeDomain() {
eventHub.$emit('saveKnativeDomain', {
id: 'knative',
params: { hostname },
params: {
hostname: this.applications.knative.hostname,
pages_domain_id: this.applications.knative.pagesDomain?.id,
},
});
},
setKnativeHostname(hostname) {
eventHub.$emit('setKnativeHostname', {
setKnativeDomain({ domainId, domain }) {
eventHub.$emit('setKnativeDomain', {
id: 'knative',
hostname,
domainId,
domain,
});
},
setCrossplaneProviderStack(stack) {
......@@ -591,7 +595,10 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
:request-reason="applications.knative.requestReason"
:installed="applications.knative.installed"
:install-failed="applications.knative.installFailed"
:install-application-request-params="{ hostname: applications.knative.hostname }"
:install-application-request-params="{
hostname: applications.knative.hostname,
pages_domain_id: applications.knative.pagesDomain && applications.knative.pagesDomain.id,
}"
:installed-via="installedVia"
:uninstallable="applications.knative.uninstallable"
:uninstall-successful="applications.knative.uninstallSuccessful"
......@@ -628,7 +635,7 @@ Crossplane runs inside your Kubernetes cluster and supports secure connectivity
:knative="knative"
:ingress-dns-help-path="ingressDnsHelpPath"
@save="saveKnativeDomain"
@set="setKnativeHostname"
@set="setKnativeDomain"
/>
</div>
</application-row>
......
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import {
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlLoadingIcon,
GlSearchBoxByType,
GlSprintf,
} from '@gitlab/ui';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
import { __, s__ } from '~/locale';
......@@ -13,6 +20,11 @@ export default {
LoadingButton,
ClipboardButton,
GlLoadingIcon,
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
GlSearchBoxByType,
GlSprintf,
},
props: {
knative: {
......@@ -25,6 +37,11 @@ export default {
required: false,
},
},
data() {
return {
searchQuery: '',
};
},
computed: {
saveButtonDisabled() {
return [UNINSTALLING, UPDATING].includes(this.knative.status);
......@@ -49,9 +66,22 @@ export default {
return this.knative.hostname;
},
set(hostname) {
this.$emit('set', hostname);
this.selectCustomDomain(hostname);
},
},
domainDropdownText() {
return this.knativeHostname || s__('ClusterIntegration|Select existing domain or use new');
},
availableDomains() {
return this.knative.availableDomains || [];
},
filteredDomains() {
const query = this.searchQuery.toLowerCase();
return this.availableDomains.filter(({ domain }) => domain.toLowerCase().includes(query));
},
showDomainsDropdown() {
return this.availableDomains.length > 0;
},
},
watch: {
knativeUpdateSuccessful(updateSuccessful) {
......@@ -60,6 +90,14 @@ export default {
}
},
},
methods: {
selectDomain({ id, domain }) {
this.$emit('set', { domain, domainId: id });
},
selectCustomDomain(domain) {
this.$emit('set', { domain, domainId: null });
},
},
};
</script>
......@@ -72,22 +110,55 @@ export default {
{{ s__('ClusterIntegration|Something went wrong while updating Knative domain name.') }}
</div>
<template>
<div
:class="{ 'col-md-6': knativeInstalled, 'col-12': !knativeInstalled }"
class="form-group col-sm-12 mb-0"
<div
:class="{ 'col-md-6': knativeInstalled, 'col-12': !knativeInstalled }"
class="form-group col-sm-12 mb-0"
>
<label for="knative-domainname">
<strong>{{ s__('ClusterIntegration|Knative Domain Name:') }}</strong>
</label>
<gl-dropdown
v-if="showDomainsDropdown"
:text="domainDropdownText"
toggle-class="dropdown-menu-toggle"
class="w-100 mb-2"
>
<label for="knative-domainname">
<strong>{{ s__('ClusterIntegration|Knative Domain Name:') }}</strong>
</label>
<input
id="knative-domainname"
v-model="knativeHostname"
type="text"
class="form-control js-knative-domainname"
<gl-search-box-by-type
v-model.trim="searchQuery"
:placeholder="s__('ClusterIntegration|Search domains')"
class="m-2"
/>
</div>
</template>
<gl-dropdown-item
v-for="domain in filteredDomains"
:key="domain.id"
@click="selectDomain(domain)"
>
<span class="ml-1">{{ domain.domain }}</span>
</gl-dropdown-item>
<template v-if="searchQuery">
<gl-dropdown-divider />
<gl-dropdown-item key="custom-domain" @click="selectCustomDomain(searchQuery)">
<span class="ml-1">
<gl-sprintf :message="s__('ClusterIntegration|Use %{query}')">
<template #query>
<code>{{ searchQuery }}</code>
</template>
</gl-sprintf>
</span>
</gl-dropdown-item>
</template>
</gl-dropdown>
<input
v-else
id="knative-domainname"
v-model="knativeHostname"
type="text"
class="form-control js-knative-domainname"
/>
</div>
<template v-if="knativeInstalled">
<div class="form-group col-sm-12 col-md-6 pl-md-0 mb-0 mt-3 mt-md-0">
<label for="knative-endpoint">
......@@ -144,7 +215,7 @@ export default {
:loading="saving"
:disabled="saveButtonDisabled"
:label="saveButtonLabel"
@click="$emit('save', knativeHostname)"
@click="$emit('save')"
/>
</template>
</div>
......
......@@ -93,7 +93,7 @@ export default class ClusterStore {
...applicationInitialState,
title: s__('ClusterIntegration|Knative'),
hostname: null,
isEditingHostName: false,
isEditingDomain: false,
externalIp: null,
externalHostname: null,
updateSuccessful: false,
......@@ -234,7 +234,12 @@ export default class ClusterStore {
'jupyter',
);
} else if (appId === KNATIVE) {
if (!this.state.applications.knative.isEditingHostName) {
if (serverAppEntry.available_domains) {
this.state.applications.knative.availableDomains = serverAppEntry.available_domains;
}
if (!this.state.applications.knative.isEditingDomain) {
this.state.applications.knative.pagesDomain =
serverAppEntry.pages_domain || this.state.applications.knative.pagesDomain;
this.state.applications.knative.hostname =
serverAppEntry.hostname || this.state.applications.knative.hostname;
}
......
......@@ -47,7 +47,7 @@ class Clusters::ApplicationsController < Clusters::BaseController
end
def cluster_application_params
params.permit(:application, :hostname, :email, :stack, :modsecurity_enabled, :modsecurity_mode)
params.permit(:application, :hostname, :pages_domain_id, :email, :stack, :modsecurity_enabled, :modsecurity_mode)
end
def cluster_application_destroy_params
......
......@@ -4694,6 +4694,9 @@ msgstr ""
msgid "ClusterIntegration|Search VPCs"
msgstr ""
msgid "ClusterIntegration|Search domains"
msgstr ""
msgid "ClusterIntegration|Search instance types"
msgstr ""
......@@ -4751,6 +4754,9 @@ msgstr ""
msgid "ClusterIntegration|Select a zone to choose a network"
msgstr ""
msgid "ClusterIntegration|Select existing domain or use new"
msgstr ""
msgid "ClusterIntegration|Select machine type"
msgstr ""
......@@ -4871,6 +4877,9 @@ msgstr ""
msgid "ClusterIntegration|Update failed. Please check the logs and try again."
msgstr ""
msgid "ClusterIntegration|Use %{query}"
msgstr ""
msgid "ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster."
msgstr ""
......
......@@ -400,6 +400,10 @@ describe('Applications', () => {
});
describe('Knative application', () => {
const availableDomain = {
id: 4,
domain: 'newhostname.com',
};
const propsData = {
applications: {
...APPLICATIONS_MOCK_STATE,
......@@ -409,10 +413,11 @@ describe('Applications', () => {
status: 'installed',
externalIp: '1.1.1.1',
installed: true,
availableDomains: [availableDomain],
pagesDomain: null,
},
},
};
const newHostname = 'newhostname.com';
let wrapper;
let knativeDomainEditor;
......@@ -428,20 +433,44 @@ describe('Applications', () => {
});
it('emits saveKnativeDomain event when knative domain editor emits save event', () => {
knativeDomainEditor.vm.$emit('save', newHostname);
propsData.applications.knative.hostname = availableDomain.domain;
propsData.applications.knative.pagesDomain = availableDomain;
knativeDomainEditor.vm.$emit('save');
expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', {
id: 'knative',
params: {
hostname: availableDomain.domain,
pages_domain_id: availableDomain.id,
},
});
});
it('emits saveKnativeDomain event when knative domain editor emits save event with custom domain', () => {
const newHostName = 'someothernewhostname.com';
propsData.applications.knative.hostname = newHostName;
propsData.applications.knative.pagesDomain = null;
knativeDomainEditor.vm.$emit('save');
expect(eventHub.$emit).toHaveBeenCalledWith('saveKnativeDomain', {
id: 'knative',
params: { hostname: newHostname },
params: {
hostname: newHostName,
pages_domain_id: undefined,
},
});
});
it('emits setKnativeHostname event when knative domain editor emits change event', () => {
wrapper.find(KnativeDomainEditor).vm.$emit('set', newHostname);
wrapper.find(KnativeDomainEditor).vm.$emit('set', {
domain: availableDomain.domain,
domainId: availableDomain.id,
});
expect(eventHub.$emit).toHaveBeenCalledWith('setKnativeHostname', {
expect(eventHub.$emit).toHaveBeenCalledWith('setKnativeDomain', {
id: 'knative',
hostname: newHostname,
domain: availableDomain.domain,
domainId: availableDomain.id,
});
});
});
......
import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem } from '@gitlab/ui';
import KnativeDomainEditor from '~/clusters/components/knative_domain_editor.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { APPLICATION_STATUS } from '~/clusters/constants';
......@@ -80,7 +81,7 @@ describe('KnativeDomainEditor', () => {
it('triggers save event and pass current knative hostname', () => {
wrapper.find(LoadingButton).vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted('save')[0]).toEqual([knative.hostname]);
expect(wrapper.emitted('save').length).toEqual(1);
});
});
});
......@@ -104,14 +105,43 @@ describe('KnativeDomainEditor', () => {
describe('when knative domain name input changes', () => {
it('emits "set" event with updated domain name', () => {
createComponent({ knative });
const newDomain = {
id: 4,
domain: 'newhostname.com',
};
createComponent({ knative: { ...knative, availableDomains: [newDomain] } });
jest.spyOn(wrapper.vm, 'selectDomain');
wrapper.find(GlDropdownItem).vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.selectDomain).toHaveBeenCalledWith(newDomain);
expect(wrapper.emitted('set')[0]).toEqual([
{
domain: newDomain.domain,
domainId: newDomain.id,
},
]);
});
});
it('emits "set" event with updated custom domain name', () => {
const newHostname = 'newhostname.com';
createComponent({ knative });
jest.spyOn(wrapper.vm, 'selectCustomDomain');
wrapper.setData({ knativeHostname: newHostname });
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.emitted('set')[0]).toEqual([newHostname]);
expect(wrapper.vm.selectCustomDomain).toHaveBeenCalledWith(newHostname);
expect(wrapper.emitted('set')[0]).toEqual([
{
domain: newHostname,
domainId: null,
},
]);
});
});
});
......
......@@ -140,7 +140,7 @@ describe('Clusters Store', () => {
statusReason: mockResponseData.applications[5].status_reason,
requestReason: null,
hostname: null,
isEditingHostName: false,
isEditingDomain: false,
externalIp: null,
externalHostname: null,
installed: false,
......
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