diff options
author | Chris Baumbauer <cab@cabnetworks.net> | 2019-01-21 16:00:16 -0800 |
---|---|---|
committer | Chris Baumbauer <cab@cabnetworks.net> | 2019-02-05 22:34:15 -0800 |
commit | 17bae7c797bbf0a78bf8b16e0423a40ee767bf21 (patch) | |
tree | 7972230c7b8a59661a9c0dd8e9b47ab3ee3bef0a | |
parent | d0187de202d94fa445a28c347b7a54dbf09a22a8 (diff) | |
download | gitlab-ce-17bae7c797bbf0a78bf8b16e0423a40ee767bf21.tar.gz |
Modified Knative list view to provide more details
16 files changed, 513 insertions, 103 deletions
diff --git a/app/assets/javascripts/serverless/components/environment_row.vue b/app/assets/javascripts/serverless/components/environment_row.vue new file mode 100644 index 00000000000..4d18c5c4bdd --- /dev/null +++ b/app/assets/javascripts/serverless/components/environment_row.vue @@ -0,0 +1,65 @@ +<script> +import FunctionRow from './function_row.vue'; +import ItemCaret from '~/groups/components/item_caret.vue'; + +export default { + components: { + ItemCaret, + FunctionRow, + }, + props: { + env: { + type: Array, + required: true, + }, + envName: { + type: String, + required: true, + }, + }, + data() { + return { + isOpen: true, + }; + }, + computed: { + envId() { + if (this.envName === '*') { + return 'env-global'; + } + + return `env-${this.envName}`; + }, + isOpenClass() { + return { + 'is-open': this.isOpen, + }; + }, + }, + methods: { + toggleOpen() { + this.isOpen = !this.isOpen; + }, + }, +}; +</script> + +<template> + <li :id="envId" :class="isOpenClass" class="group-row has-children"> + <div + class="group-row-contents d-flex justify-content-end align-items-center" + role="button" + @click.stop="toggleOpen" + > + <div class="folder-toggle-wrap d-flex align-items-center"> + <item-caret :is-group-open="isOpen" /> + </div> + <div class="group-text flex-grow title namespace-title prepend-left-default"> + {{ envName }} + </div> + </div> + <ul v-if="isOpen" class="content-list group-list-tree"> + <function-row v-for="(f, index) in env" :key="f.name" :index="index" :func="f" /> + </ul> + </li> +</template> diff --git a/app/assets/javascripts/serverless/components/function_details.vue b/app/assets/javascripts/serverless/components/function_details.vue index 2b1c21f041b..4f89ad69129 100644 --- a/app/assets/javascripts/serverless/components/function_details.vue +++ b/app/assets/javascripts/serverless/components/function_details.vue @@ -1,13 +1,11 @@ <script> import PodBox from './pod_box.vue'; -import ClipboardButton from '../../vue_shared/components/clipboard_button.vue'; -import Icon from '~/vue_shared/components/icon.vue'; +import Url from './url.vue'; export default { components: { - Icon, PodBox, - ClipboardButton, + Url, }, props: { func: { @@ -36,24 +34,9 @@ export default { <section id="serverless-function-details"> <h3>{{ name }}</h3> <div class="append-bottom-default"> - <div v-for="line in description.split('\n')" :key="line">{{ line }}<br /></div> - </div> - <div class="clipboard-group append-bottom-default"> - <div class="label label-monospace">{{ funcUrl }}</div> - <clipboard-button - :text="String(funcUrl)" - :title="s__('ServerlessDetails|Copy URL to clipboard')" - class="input-group-text js-clipboard-btn" - /> - <a - :href="funcUrl" - target="_blank" - rel="noopener noreferrer nofollow" - class="input-group-text btn btn-default" - > - <icon name="external-link" /> - </a> + <div v-for="(line, index) in description.split('\n')" :key="index">{{ line }}</div> </div> + <url :uri="funcUrl" /> <h4>{{ s__('ServerlessDetails|Kubernetes Pods') }}</h4> <div v-if="podCount > 0"> diff --git a/app/assets/javascripts/serverless/components/function_row.vue b/app/assets/javascripts/serverless/components/function_row.vue index 44bfae388cb..773d18781fd 100644 --- a/app/assets/javascripts/serverless/components/function_row.vue +++ b/app/assets/javascripts/serverless/components/function_row.vue @@ -1,9 +1,12 @@ <script> import Timeago from '~/vue_shared/components/time_ago_tooltip.vue'; +import Url from './url.vue'; +import { visitUrl } from '~/lib/utils/url_utility'; export default { components: { Timeago, + Url, }, props: { func: { @@ -16,13 +19,18 @@ export default { return this.func.name; }, description() { - return this.func.description; + const desc = this.func.description.split('\n'); + if (desc.length > 1) { + return desc[1]; + } + + return desc[0]; }, detailUrl() { return this.func.detail_url; }, - environment() { - return this.func.environment_scope; + targetUrl() { + return this.func.url; }, image() { return this.func.image; @@ -31,25 +39,34 @@ export default { return this.func.created_at; }, }, + methods: { + checkClass(element) { + if (element.closest('.no-expand') === null) { + return true; + } + + return false; + }, + openDetails(e) { + if (this.checkClass(e.target)) { + visitUrl(this.detailUrl); + } + }, + }, }; </script> <template> - <div class="gl-responsive-table-row"> - <div class="table-section section-20 section-wrap"> - <a :href="detailUrl">{{ name }}</a> - </div> - <div class="table-section section-10">{{ environment }}</div> - <div class="table-section section-40 section-wrap"> - <span class="line-break">{{ description }}</span> + <li :id="name" class="group-row"> + <div class="group-row-contents" role="button" @click="openDetails"> + <p class="float-right text-right"> + <span>{{ image }}</span + ><br /> + <timeago :time="timestamp" /> + </p> + <b>{{ name }}</b> + <div v-for="line in description.split('\n')" :key="line">{{ line }}</div> + <url :uri="targetUrl" class="prepend-top-8 no-expand" /> </div> - <div class="table-section section-20">{{ image }}</div> - <div class="table-section section-10"><timeago :time="timestamp" /></div> - </div> + </li> </template> - -<style> -.line-break { - white-space: pre; -} -</style> diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue index 9606a78410e..4bde409f906 100644 --- a/app/assets/javascripts/serverless/components/functions.vue +++ b/app/assets/javascripts/serverless/components/functions.vue @@ -1,19 +1,21 @@ <script> import { GlSkeletonLoading } from '@gitlab/ui'; import FunctionRow from './function_row.vue'; +import EnvironmentRow from './environment_row.vue'; import EmptyState from './empty_state.vue'; export default { components: { + EnvironmentRow, FunctionRow, EmptyState, GlSkeletonLoading, }, props: { functions: { - type: Array, + type: Object, required: true, - default: () => [], + default: () => ({}), }, installed: { type: Boolean, @@ -45,33 +47,21 @@ export default { <section id="serverless-functions"> <div v-if="installed"> <div v-if="hasFunctionData"> - <div class="ci-table js-services-list function-element"> - <div class="gl-responsive-table-row table-row-header" role="row"> - <div class="table-section section-20" role="rowheader"> - {{ s__('Serverless|Function') }} - </div> - <div class="table-section section-10" role="rowheader"> - {{ s__('Serverless|Cluster Env') }} - </div> - <div class="table-section section-40" role="rowheader"> - {{ s__('Serverless|Description') }} - </div> - <div class="table-section section-20" role="rowheader"> - {{ s__('Serverless|Runtime') }} - </div> - <div class="table-section section-10" role="rowheader"> - {{ s__('Serverless|Last Update') }} - </div> + <template v-if="loadingData"> + <div v-for="j in 3" :key="j" class="gl-responsive-table-row"><gl-skeleton-loading /></div> + </template> + <template v-else> + <div class="groups-list-tree-container"> + <ul class="content-list group-list-tree"> + <environment-row + v-for="(env, index) in functions" + :key="index" + :env="env" + :env-name="index" + /> + </ul> </div> - <template v-if="loadingData"> - <div v-for="j in 3" :key="j" class="gl-responsive-table-row"> - <gl-skeleton-loading /> - </div> - </template> - <template v-else> - <function-row v-for="f in functions" :key="f.name" :func="f" /> - </template> - </div> + </template> </div> <div v-else class="empty-state js-empty-state"> <div class="text-content"> @@ -111,16 +101,3 @@ export default { <empty-state v-else :clusters-path="clustersPath" :help-path="helpPath" /> </section> </template> - -<style> -.top-area { - border-bottom: 0; -} - -.function-element { - border-bottom: 1px solid #e5e5e5; - border-bottom-color: rgb(229, 229, 229); - border-bottom-style: solid; - border-bottom-width: 1px; -} -</style> diff --git a/app/assets/javascripts/serverless/components/url.vue b/app/assets/javascripts/serverless/components/url.vue new file mode 100644 index 00000000000..ca53bf6c52a --- /dev/null +++ b/app/assets/javascripts/serverless/components/url.vue @@ -0,0 +1,38 @@ +<script> +import { GlButton } from '@gitlab/ui'; +import ClipboardButton from '../../vue_shared/components/clipboard_button.vue'; +import Icon from '~/vue_shared/components/icon.vue'; + +export default { + components: { + Icon, + GlButton, + ClipboardButton, + }, + props: { + uri: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <div class="clipboard-group"> + <div class="url-text-field label label-monospace">{{ uri }}</div> + <clipboard-button + :text="uri" + :title="s__('ServerlessURL|Copy URL to clipboard')" + class="input-group-text js-clipboard-btn" + /> + <gl-button + :href="uri" + target="_blank" + rel="noopener noreferrer nofollow" + class="input-group-text btn btn-default" + > + <icon name="external-link" /> + </gl-button> + </div> +</template> diff --git a/app/assets/javascripts/serverless/stores/serverless_store.js b/app/assets/javascripts/serverless/stores/serverless_store.js index 774c15b5b12..816d55a03f9 100644 --- a/app/assets/javascripts/serverless/stores/serverless_store.js +++ b/app/assets/javascripts/serverless/stores/serverless_store.js @@ -1,7 +1,7 @@ export default class ServerlessStore { constructor(knativeInstalled = false, clustersPath, helpPath) { this.state = { - functions: [], + functions: {}, hasFunctionData: true, loadingData: true, installed: knativeInstalled, @@ -10,8 +10,13 @@ export default class ServerlessStore { }; } - updateFunctionsFromServer(functions = []) { - this.state.functions = functions; + updateFunctionsFromServer(upstreamFunctions = []) { + this.state.functions = upstreamFunctions.reduce((rv, func) => { + const envs = rv; + envs[func.environment_scope] = (rv[func.environment_scope] || []).concat([func]); + + return envs; + }, {}); } updateLoadingState(loadingData) { diff --git a/app/assets/stylesheets/pages/serverless.scss b/app/assets/stylesheets/pages/serverless.scss new file mode 100644 index 00000000000..a5b73492380 --- /dev/null +++ b/app/assets/stylesheets/pages/serverless.scss @@ -0,0 +1,3 @@ +.url-text-field { + cursor: text; +} diff --git a/changelogs/unreleased/knative-list.yml b/changelogs/unreleased/knative-list.yml new file mode 100644 index 00000000000..754d8e172cf --- /dev/null +++ b/changelogs/unreleased/knative-list.yml @@ -0,0 +1,5 @@ +--- +title: Modified Knative list view to provide more details +merge_request: 24072 +author: Chris Baumbauer +type: changed diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6687cdd5feb..b78a0e5e5f3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6360,9 +6360,6 @@ msgstr "" msgid "Serverless" msgstr "" -msgid "ServerlessDetails|Copy URL to clipboard" -msgstr "" - msgid "ServerlessDetails|Kubernetes Pods" msgstr "" @@ -6375,19 +6372,13 @@ msgstr "" msgid "ServerlessDetails|pods in use" msgstr "" -msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster." -msgstr "" - -msgid "Serverless|An error occurred while retrieving serverless components" -msgstr "" - -msgid "Serverless|Cluster Env" +msgid "ServerlessURL|Copy URL to clipboard" msgstr "" -msgid "Serverless|Description" +msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster." msgstr "" -msgid "Serverless|Function" +msgid "Serverless|An error occurred while retrieving serverless components" msgstr "" msgid "Serverless|Getting started with serverless" @@ -6399,18 +6390,12 @@ msgstr "" msgid "Serverless|Install Knative" msgstr "" -msgid "Serverless|Last Update" -msgstr "" - msgid "Serverless|Learn more about Serverless" msgstr "" msgid "Serverless|No functions available" msgstr "" -msgid "Serverless|Runtime" -msgstr "" - msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:" msgstr "" diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb index 766c63725b3..aa71669de98 100644 --- a/spec/features/projects/serverless/functions_spec.rb +++ b/spec/features/projects/serverless/functions_spec.rb @@ -1,6 +1,10 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Functions', :js do + include KubernetesHelpers + let(:project) { create(:project) } let(:user) { create(:user) } @@ -34,11 +38,14 @@ describe 'Functions', :js do end context 'when the user has a cluster and knative installed and visits the serverless page' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:service) { cluster.platform_kubernetes } let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } let(:project) { knative.cluster.project } before do + stub_kubeclient_knative_services + stub_kubeclient_service_pods visit project_serverless_functions_path(project) end diff --git a/spec/javascripts/serverless/components/environment_row_spec.js b/spec/javascripts/serverless/components/environment_row_spec.js new file mode 100644 index 00000000000..bdf7a714910 --- /dev/null +++ b/spec/javascripts/serverless/components/environment_row_spec.js @@ -0,0 +1,81 @@ +import Vue from 'vue'; + +import environmentRowComponent from '~/serverless/components/environment_row.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import ServerlessStore from '~/serverless/stores/serverless_store'; + +import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; + +const createComponent = (env, envName) => + mountComponent(Vue.extend(environmentRowComponent), { env, envName }); + +describe('environment row component', () => { + describe('default global cluster case', () => { + let vm; + + beforeEach(() => { + const store = new ServerlessStore(false, '/cluster_path', 'help_path'); + store.updateFunctionsFromServer(mockServerlessFunctions); + vm = createComponent(store.state.functions['*'], '*'); + }); + + it('has the correct envId', () => { + expect(vm.envId).toEqual('env-global'); + vm.$destroy(); + }); + + it('is open by default', () => { + expect(vm.isOpenClass).toEqual({ 'is-open': true }); + vm.$destroy(); + }); + + it('generates correct output', () => { + expect(vm.$el.querySelectorAll('li').length).toEqual(2); + expect(vm.$el.id).toEqual('env-global'); + expect(vm.$el.classList.contains('is-open')).toBe(true); + expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('*'); + + vm.$destroy(); + }); + + it('opens and closes correctly', () => { + expect(vm.isOpen).toBe(true); + + vm.toggleOpen(); + Vue.nextTick(() => { + expect(vm.isOpen).toBe(false); + }); + + vm.$destroy(); + }); + }); + + describe('default named cluster case', () => { + let vm; + + beforeEach(() => { + const store = new ServerlessStore(false, '/cluster_path', 'help_path'); + store.updateFunctionsFromServer(mockServerlessFunctionsDiffEnv); + vm = createComponent(store.state.functions.test, 'test'); + }); + + it('has the correct envId', () => { + expect(vm.envId).toEqual('env-test'); + vm.$destroy(); + }); + + it('is open by default', () => { + expect(vm.isOpenClass).toEqual({ 'is-open': true }); + vm.$destroy(); + }); + + it('generates correct output', () => { + expect(vm.$el.querySelectorAll('li').length).toEqual(1); + expect(vm.$el.id).toEqual('env-test'); + expect(vm.$el.classList.contains('is-open')).toBe(true); + expect(vm.$el.querySelector('div.title').innerHTML.trim()).toEqual('test'); + + vm.$destroy(); + }); + }); +}); diff --git a/spec/javascripts/serverless/components/function_row_spec.js b/spec/javascripts/serverless/components/function_row_spec.js new file mode 100644 index 00000000000..6933a8f6c87 --- /dev/null +++ b/spec/javascripts/serverless/components/function_row_spec.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; + +import functionRowComponent from '~/serverless/components/function_row.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +import { mockServerlessFunction } from '../mock_data'; + +const createComponent = func => mountComponent(Vue.extend(functionRowComponent), { func }); + +describe('functionRowComponent', () => { + it('Parses the function details correctly', () => { + const vm = createComponent(mockServerlessFunction); + + expect(vm.$el.querySelector('b').innerHTML).toEqual(mockServerlessFunction.name); + expect(vm.$el.querySelector('span').innerHTML).toEqual(mockServerlessFunction.image); + expect(vm.$el.querySelector('time').getAttribute('data-original-title')).not.toBe(null); + expect(vm.$el.querySelector('div.url-text-field').innerHTML).toEqual( + mockServerlessFunction.url, + ); + + vm.$destroy(); + }); + + it('handles clicks correctly', () => { + const vm = createComponent(mockServerlessFunction); + + expect(vm.checkClass(vm.$el.querySelector('p'))).toBe(true); // check somewhere inside the row + expect(vm.checkClass(vm.$el.querySelector('svg'))).toBe(false); // check a button image + expect(vm.checkClass(vm.$el.querySelector('div.url-text-field'))).toBe(false); // check the url bar + + vm.$destroy(); + }); +}); diff --git a/spec/javascripts/serverless/components/functions_spec.js b/spec/javascripts/serverless/components/functions_spec.js new file mode 100644 index 00000000000..85cfe71281f --- /dev/null +++ b/spec/javascripts/serverless/components/functions_spec.js @@ -0,0 +1,68 @@ +import Vue from 'vue'; + +import functionsComponent from '~/serverless/components/functions.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import ServerlessStore from '~/serverless/stores/serverless_store'; + +import { mockServerlessFunctions } from '../mock_data'; + +const createComponent = ( + functions, + installed = true, + loadingData = true, + hasFunctionData = true, +) => { + const component = Vue.extend(functionsComponent); + + return mountComponent(component, { + functions, + installed, + clustersPath: '/testClusterPath', + helpPath: '/helpPath', + loadingData, + hasFunctionData, + }); +}; + +describe('functionsComponent', () => { + it('should render empty state when Knative is not installed', () => { + const vm = createComponent({}, false); + + expect(vm.$el.querySelector('div.row').classList.contains('js-empty-state')).toBe(true); + expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual( + 'Getting started with serverless', + ); + + vm.$destroy(); + }); + + it('should render a loading component', () => { + const vm = createComponent({}); + + expect(vm.$el.querySelector('.gl-responsive-table-row')).not.toBe(null); + expect(vm.$el.querySelector('div.animation-container')).not.toBe(null); + }); + + it('should render empty state when there is no function data', () => { + const vm = createComponent({}, true, false, false); + + expect( + vm.$el.querySelector('.empty-state, .js-empty-state').classList.contains('js-empty-state'), + ).toBe(true); + + expect(vm.$el.querySelector('h4.state-title').innerHTML.trim()).toEqual( + 'No functions available', + ); + + vm.$destroy(); + }); + + it('should render the functions list', () => { + const store = new ServerlessStore(false, '/cluster_path', 'help_path'); + store.updateFunctionsFromServer(mockServerlessFunctions); + const vm = createComponent(store.state.functions, true, false); + + expect(vm.$el.querySelector('div.groups-list-tree-container')).not.toBe(null); + expect(vm.$el.querySelector('#env-global').classList.contains('has-children')).toBe(true); + }); +}); diff --git a/spec/javascripts/serverless/components/url_spec.js b/spec/javascripts/serverless/components/url_spec.js new file mode 100644 index 00000000000..21a879a49bb --- /dev/null +++ b/spec/javascripts/serverless/components/url_spec.js @@ -0,0 +1,28 @@ +import Vue from 'vue'; + +import urlComponent from '~/serverless/components/url.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +const createComponent = uri => { + const component = Vue.extend(urlComponent); + + return mountComponent(component, { + uri, + }); +}; + +describe('urlComponent', () => { + it('should render correctly', () => { + const uri = 'http://testfunc.apps.example.com'; + const vm = createComponent(uri); + + expect(vm.$el.classList.contains('clipboard-group')).toBe(true); + expect(vm.$el.querySelector('.js-clipboard-btn').getAttribute('data-clipboard-text')).toEqual( + uri, + ); + + expect(vm.$el.querySelector('.url-text-field').innerHTML).toEqual(uri); + + vm.$destroy(); + }); +}); diff --git a/spec/javascripts/serverless/mock_data.js b/spec/javascripts/serverless/mock_data.js new file mode 100644 index 00000000000..ecd393b174c --- /dev/null +++ b/spec/javascripts/serverless/mock_data.js @@ -0,0 +1,79 @@ +export const mockServerlessFunctions = [ + { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', + }, + { + name: 'testfunc2', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc2.tm-example.apps.example.com', + description: 'A second test service\nThis one with additional descriptions', + image: 'knative-test-echo-buildtemplate', + }, +]; + +export const mockServerlessFunctionsDiffEnv = [ + { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', + }, + { + name: 'testfunc2', + namespace: 'tm-example', + environment_scope: 'test', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc2', + podcount: null, + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc2.tm-example.apps.example.com', + description: 'A second test service\nThis one with additional descriptions', + image: 'knative-test-echo-buildtemplate', + }, +]; + +export const mockServerlessFunction = { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: '3', + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'A test service', + image: 'knative-test-container-buildtemplate', +}; + +export const mockMultilineServerlessFunction = { + name: 'testfunc1', + namespace: 'tm-example', + environment_scope: '*', + cluster_id: 46, + detail_url: '/testuser/testproj/serverless/functions/*/testfunc1', + podcount: '3', + created_at: '2019-02-05T01:01:23Z', + url: 'http://testfunc1.tm-example.apps.example.com', + description: 'testfunc1\nA test service line\\nWith additional services', + image: 'knative-test-container-buildtemplate', +}; diff --git a/spec/javascripts/serverless/stores/serverless_store_spec.js b/spec/javascripts/serverless/stores/serverless_store_spec.js new file mode 100644 index 00000000000..72fd903d7d1 --- /dev/null +++ b/spec/javascripts/serverless/stores/serverless_store_spec.js @@ -0,0 +1,36 @@ +import ServerlessStore from '~/serverless/stores/serverless_store'; +import { mockServerlessFunctions, mockServerlessFunctionsDiffEnv } from '../mock_data'; + +describe('Serverless Functions Store', () => { + let store; + + beforeEach(() => { + store = new ServerlessStore(false, '/cluster_path', 'help_path'); + }); + + describe('#updateFunctionsFromServer', () => { + it('should pass an empty hash object', () => { + store.updateFunctionsFromServer(); + + expect(store.state.functions).toEqual({}); + }); + + it('should group functions to one global environment', () => { + const mockServerlessData = mockServerlessFunctions; + store.updateFunctionsFromServer(mockServerlessData); + + expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*'])); + expect(store.state.functions['*'].length).toEqual(2); + }); + + it('should group functions to multiple environments', () => { + const mockServerlessData = mockServerlessFunctionsDiffEnv; + store.updateFunctionsFromServer(mockServerlessData); + + expect(Object.keys(store.state.functions)).toEqual(jasmine.objectContaining(['*'])); + expect(store.state.functions['*'].length).toEqual(1); + expect(store.state.functions.test.length).toEqual(1); + expect(store.state.functions.test[0].name).toEqual('testfunc2'); + }); + }); +}); |