Commit 087f9155 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch '25483-serverless-empty-state' into 'master'

Serverless empty state update

Closes #25483

See merge request gitlab-org/gitlab!36762
parents 0a8fe94b 45c8d030
<script>
import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import { mapState } from 'vuex';
export default {
props: {
clustersPath: {
type: String,
required: true,
},
helpPath: {
type: String,
required: true,
components: {
GlEmptyState,
GlLink,
GlSprintf,
},
computed: {
...mapState(['clustersPath', 'emptyImagePath', 'helpPath']),
},
};
</script>
<template>
<div class="row empty-state js-empty-state">
<div class="col-12">
<div class="text-content">
<h4 class="state-title text-center">
{{ s__('Serverless|Getting started with serverless') }}
</h4>
<p class="state-description">
{{
s__(`Serverless| In order to start using functions as a service,
you must first install Knative on your Kubernetes cluster.`)
}}
<a :href="helpPath"> {{ __('More information') }} </a>
</p>
<div class="text-center">
<a :href="clustersPath" class="btn btn-success">
{{ s__('Serverless|Install Knative') }}
</a>
</div>
</div>
</div>
</div>
<gl-empty-state
:svg-path="emptyImagePath"
:title="s__('Serverless|Getting started with serverless')"
:primary-button-link="clustersPath"
:primary-button-text="s__('Serverless|Install Knative')"
>
<template #description>
<gl-sprintf
:message="
s__(
'Serverless|In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. %{linkStart}More information%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link :href="helpPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</template>
</gl-empty-state>
</template>
......@@ -23,14 +23,6 @@ export default {
required: false,
default: false,
},
clustersPath: {
type: String,
required: true,
},
helpPath: {
type: String,
required: true,
},
},
data() {
return {
......@@ -96,8 +88,6 @@ export default {
<area-chart v-if="hasPrometheusData" :graph-data="graphData" :container-width="elWidth" />
<missing-prometheus
v-if="!hasPrometheus || hasPrometheusMissingData"
:help-path="helpPath"
:clusters-path="clustersPath"
:missing-data="hasPrometheusMissingData"
/>
</section>
......
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLink, GlLoadingIcon } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import EnvironmentRow from './environment_row.vue';
import EmptyState from './empty_state.vue';
......@@ -10,24 +10,11 @@ export default {
components: {
EnvironmentRow,
EmptyState,
GlLink,
GlLoadingIcon,
},
props: {
clustersPath: {
type: String,
required: true,
},
helpPath: {
type: String,
required: true,
},
statusPath: {
type: String,
required: true,
},
},
computed: {
...mapState(['installed', 'isLoading', 'hasFunctionData']),
...mapState(['installed', 'isLoading', 'hasFunctionData', 'helpPath', 'statusPath']),
...mapGetters(['getFunctions']),
checkingInstalled() {
......@@ -118,14 +105,14 @@ export default {
}}
</p>
<div class="text-center">
<a :href="helpPath" class="btn btn-success">
{{ s__('Serverless|Learn more about Serverless') }}
</a>
<gl-link :href="helpPath" class="btn btn-success">{{
s__('Serverless|Learn more about Serverless')
}}</gl-link>
</div>
</div>
</div>
</div>
<empty-state v-else :clusters-path="clustersPath" :help-path="helpPath" />
<empty-state v-else />
</section>
</template>
<script>
import { GlDeprecatedButton, GlLink } from '@gitlab/ui';
import { mapState } from 'vuex';
import { s__ } from '../../locale';
export default {
......@@ -8,20 +9,13 @@ export default {
GlLink,
},
props: {
clustersPath: {
type: String,
required: true,
},
helpPath: {
type: String,
required: true,
},
missingData: {
type: Boolean,
required: true,
},
},
computed: {
...mapState(['clustersPath', 'helpPath']),
missingStateClass() {
return this.missingData ? 'missing-prometheus-state' : 'empty-prometheus-state';
},
......
......@@ -6,6 +6,9 @@ import { createStore } from './store';
export default class Serverless {
constructor() {
if (document.querySelector('.js-serverless-function-details-page') != null) {
const entryPointData = document.querySelector('.js-serverless-function-details-page').dataset;
const store = createStore(entryPointData);
const {
serviceName,
serviceDescription,
......@@ -15,9 +18,7 @@ export default class Serverless {
servicePodcount,
serviceMetricsUrl,
prometheus,
clustersPath,
helpPath,
} = document.querySelector('.js-serverless-function-details-page').dataset;
} = entryPointData;
const el = document.querySelector('#js-serverless-function-details');
const service = {
......@@ -32,35 +33,26 @@ export default class Serverless {
this.functionDetails = new Vue({
el,
store: createStore(),
store,
render(createElement) {
return createElement(FunctionDetails, {
props: {
func: service,
hasPrometheus: prometheus !== undefined,
clustersPath,
helpPath,
},
});
},
});
} else {
const { statusPath, clustersPath, helpPath } = document.querySelector(
'.js-serverless-functions-page',
).dataset;
const entryPointData = document.querySelector('.js-serverless-functions-page').dataset;
const store = createStore(entryPointData);
const el = document.querySelector('#js-serverless-functions');
this.functions = new Vue({
el,
store: createStore(),
store,
render(createElement) {
return createElement(Functions, {
props: {
clustersPath,
helpPath,
statusPath,
},
});
return createElement(Functions);
},
});
}
......
......@@ -7,12 +7,12 @@ import createState from './state';
Vue.use(Vuex);
export const createStore = () =>
export const createStore = (entryPointData = {}) =>
new Vuex.Store({
actions,
getters,
mutations,
state: createState(),
state: createState(entryPointData),
});
export default createStore();
export default createStore;
export default () => ({
export default (
initialState = { clustersPath: null, helpPath: null, emptyImagePath: null, statusPath: null },
) => ({
clustersPath: initialState.clustersPath,
error: null,
helpPath: initialState.helpPath,
installed: 'checking',
isLoading: true,
// functions
functions: [],
hasFunctionData: true,
statusPath: initialState.statusPath,
// function_details
hasPrometheus: true,
hasPrometheusData: false,
graphData: {},
// empty_state
emptyImagePath: initialState.emptyImagePath,
});
......@@ -7,7 +7,8 @@
.serverless-functions-page.js-serverless-functions-page{ data: { status_path: status_path,
installed: @installed,
clusters_path: clusters_path,
help_path: help_page_path('user/project/clusters/serverless/index') } }
help_path: help_page_path('user/project/clusters/serverless/index'),
empty_image_path: image_path('illustrations/empty-state/empty-serverless-lg.svg') } }
%div{ class: [('limit-container-width' unless fluid_layout)] }
.js-serverless-survey-banner{ data: { user_name: current_user.name, user_email: current_user.email } }
......@@ -15,5 +16,5 @@
.js-serverless-functions-notice
.flash-container
.top-area.adjust.d-flex.justify-content-center
.top-area.adjust.d-flex.justify-content-center.gl-border-none
.serverless-functions-table#js-serverless-functions
---
title: Add serverless empty state illustration
merge_request: 36762
author:
type: changed
......@@ -21387,9 +21387,6 @@ msgstr ""
msgid "ServerlessURL|Copy URL"
msgstr ""
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
msgstr ""
msgid "Serverless|Getting started with serverless"
msgstr ""
......@@ -21399,6 +21396,9 @@ msgstr ""
msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
msgstr ""
msgid "Serverless|In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "Serverless|Install Knative"
msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EmptyStateComponent should render content 1`] = `
"<section class=\\"row empty-state text-center\\">
<div class=\\"col-12\\">
<div class=\\"svg-250 svg-content\\"><img src=\\"/image.svg\\" alt=\\"Getting started with serverless\\" class=\\"gl-max-w-full\\"></div>
</div>
<div class=\\"col-12\\">
<div class=\\"text-content gl-mx-auto gl-my-0 gl-p-5\\">
<h1 class=\\"h4\\">Getting started with serverless</h1>
<p>In order to start using functions as a service, you must first install Knative on your Kubernetes cluster. <gl-link-stub href=\\"/help\\">More information</gl-link-stub>
</p>
<div>
<gl-button-stub category=\\"tertiary\\" variant=\\"success\\" size=\\"medium\\" icon=\\"\\" href=\\"/clusters\\">Install Knative</gl-button-stub>
<!---->
</div>
</div>
</div>
</section>"
`;
import { createStore } from '~/serverless/store';
import { GlEmptyState, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import EmptyStateComponent from '~/serverless/components/empty_state.vue';
describe('EmptyStateComponent', () => {
let wrapper;
beforeEach(() => {
const store = createStore({
clustersPath: '/clusters',
helpPath: '/help',
emptyImagePath: '/image.svg',
});
wrapper = shallowMount(EmptyStateComponent, { store, stubs: { GlEmptyState, GlSprintf } });
});
afterEach(() => {
wrapper.destroy();
});
it('should render content', () => {
expect(wrapper.html()).toMatchSnapshot();
});
});
......@@ -13,7 +13,7 @@ describe('functionDetailsComponent', () => {
localVue = createLocalVue();
localVue.use(Vuex);
store = createStore();
store = createStore({ clustersPath: '/clusters', helpPath: '/help' });
});
afterEach(() => {
......@@ -38,8 +38,6 @@ describe('functionDetailsComponent', () => {
propsData: {
func: serviceStub,
hasPrometheus: false,
clustersPath: '/clusters',
helpPath: '/help',
},
});
......@@ -65,8 +63,6 @@ describe('functionDetailsComponent', () => {
propsData: {
func: serviceStub,
hasPrometheus: false,
clustersPath: '/clusters',
helpPath: '/help',
},
});
......@@ -82,8 +78,6 @@ describe('functionDetailsComponent', () => {
propsData: {
func: serviceStub,
hasPrometheus: false,
clustersPath: '/clusters',
helpPath: '/help',
},
});
......@@ -99,8 +93,6 @@ describe('functionDetailsComponent', () => {
propsData: {
func: serviceStub,
hasPrometheus: false,
clustersPath: '/clusters',
helpPath: '/help',
},
});
......
......@@ -25,55 +25,31 @@ describe('functionsComponent', () => {
localVue = createLocalVue();
localVue.use(Vuex);
store = createStore();
store = createStore({});
});
afterEach(() => {
component.vm.$destroy();
component.destroy();
axiosMock.restore();
});
it('should render empty state when Knative is not installed', () => {
store.dispatch('receiveFunctionsSuccess', { knative_installed: false });
component = shallowMount(functionsComponent, {
localVue,
store,
propsData: {
clustersPath: '',
helpPath: '',
statusPath: '',
},
});
component = shallowMount(functionsComponent, { localVue, store });
expect(component.find(EmptyState).exists()).toBe(true);
});
it('should render a loading component', () => {
store.dispatch('requestFunctionsLoading');
component = shallowMount(functionsComponent, {
localVue,
store,
propsData: {
clustersPath: '',
helpPath: '',
statusPath: '',
},
});
component = shallowMount(functionsComponent, { localVue, store });
expect(component.find(GlLoadingIcon).exists()).toBe(true);
});
it('should render empty state when there is no function data', () => {
store.dispatch('receiveFunctionsNoDataSuccess', { knative_installed: true });
component = shallowMount(functionsComponent, {
localVue,
store,
propsData: {
clustersPath: '',
helpPath: '',
statusPath: '',
},
});
component = shallowMount(functionsComponent, { localVue, store });
expect(
component.vm.$el
......@@ -91,30 +67,17 @@ describe('functionsComponent', () => {
...mockServerlessFunctions,
knative_installed: 'checking',
});
component = shallowMount(functionsComponent, {
localVue,
store,
propsData: {
clustersPath: '',
helpPath: '',
statusPath: '',
},
});
component = shallowMount(functionsComponent, { localVue, store });
expect(component.find('.js-functions-wrapper').exists()).toBe(true);
expect(component.find('.js-functions-loader').exists()).toBe(true);
});
it('should render the functions list', () => {
component = shallowMount(functionsComponent, {
localVue,
store,
propsData: {
clustersPath: 'clustersPath',
helpPath: 'helpPath',
statusPath,
},
});
store = createStore({ clustersPath: 'clustersPath', helpPath: 'helpPath', statusPath });
component = shallowMount(functionsComponent, { localVue, store });
component.vm.$store.dispatch('receiveFunctionsSuccess', mockServerlessFunctions);
......
import { createStore } from '~/serverless/store';
import { GlDeprecatedButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import missingPrometheusComponent from '~/serverless/components/missing_prometheus.vue';
const createComponent = missingData =>
shallowMount(missingPrometheusComponent, {
propsData: {
clustersPath: '/clusters',
helpPath: '/help',
missingData,
},
});
describe('missingPrometheusComponent', () => {
let wrapper;
const createComponent = missingData => {
const store = createStore({ clustersPath: '/clusters', helpPath: '/help' });
wrapper = shallowMount(missingPrometheusComponent, { store, propsData: { missingData } });
};
afterEach(() => {
wrapper.destroy();
});
it('should render missing prometheus message', () => {
wrapper = createComponent(false);
createComponent(false);
const { vm } = wrapper;
expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain(
......@@ -30,7 +28,7 @@ describe('missingPrometheusComponent', () => {
});
it('should render no prometheus data message', () => {
wrapper = createComponent(true);
createComponent(true);
const { vm } = wrapper;
expect(vm.$el.querySelector('.state-description').innerHTML.trim()).toContain(
......
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