Commit cdd8027a authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents 84a8d5c4 c5f1b834
......@@ -3,12 +3,14 @@ import { GlAreaChart } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue';
let debouncedResize;
export default {
components: {
GlAreaChart,
Icon,
},
inheritAttrs: false,
props: {
......@@ -47,6 +49,12 @@ export default {
},
data() {
return {
tooltip: {
title: '',
content: '',
isDeployment: false,
sha: '',
},
width: 0,
height: 0,
scatterSymbol: undefined,
......@@ -148,8 +156,17 @@ export default {
},
methods: {
formatTooltipText(params) {
const [date, value] = params;
return [dateFormat(date, 'dd mmm yyyy, h:MMtt'), value.toFixed(3)];
const [seriesData] = params.seriesData;
this.tooltip.isDeployment = seriesData.componentSubType === 'scatter';
this.tooltip.title = dateFormat(params.value, 'dd mmm yyyy, h:MMTT');
if (this.tooltip.isDeployment) {
const [deploy] = this.recentDeployments.filter(
deployment => deployment.createdAt === seriesData.value[0],
);
this.tooltip.sha = deploy.sha.substring(0, 8);
} else {
this.tooltip.content = `${this.yAxisLabel} ${seriesData.value[1].toFixed(3)}`;
}
},
getScatterSymbol() {
getSvgIconPathContent('rocket')
......@@ -184,6 +201,22 @@ export default {
:thresholds="alertData"
:width="width"
:height="height"
/>
>
<template slot="tooltipTitle">
<div v-if="tooltip.isDeployment">
{{ __('Deployed') }}
</div>
{{ tooltip.title }}
</template>
<template slot="tooltipContent">
<div v-if="tooltip.isDeployment" class="d-flex align-items-center">
<icon name="commit" class="mr-2" />
{{ tooltip.sha }}
</div>
<template v-else>
{{ tooltip.content }}
</template>
</template>
</gl-area-chart>
</div>
</template>
<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>
<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">
......
<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>
<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>
<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>
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) {
......
.url-text-field {
cursor: text;
}
---
title: Modified Knative list view to provide more details
merge_request: 24072
author: Chris Baumbauer
type: changed
......@@ -3095,6 +3095,9 @@ msgstr ""
msgid "DeployTokens|Your new project deploy token has been created."
msgstr ""
msgid "Deployed"
msgstr ""
msgid "Deployed to"
msgstr ""
......@@ -8268,9 +8271,6 @@ msgstr ""
msgid "Serverless"
msgstr ""
msgid "ServerlessDetails|Copy URL to clipboard"
msgstr ""
msgid "ServerlessDetails|Kubernetes Pods"
msgstr ""
......@@ -8283,19 +8283,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"
......@@ -8307,18 +8301,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 ""
......
# 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
......
import $ from 'jquery';
import _ from 'underscore';
import Vue from 'vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import createStore from '~/notes/stores';
import issueNote from '~/notes/components/noteable_note.vue';
import NoteHeader from '~/notes/components/note_header.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import NoteActions from '~/notes/components/note_actions.vue';
import NoteBody from '~/notes/components/note_body.vue';
import { noteableDataMock, notesDataMock, note } from '../mock_data';
describe('issue_note', () => {
let store;
let vm;
let wrapper;
beforeEach(() => {
const Component = Vue.extend(issueNote);
store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
vm = new Component({
const localVue = createLocalVue();
wrapper = shallowMount(issueNote, {
store,
propsData: {
note,
},
}).$mount();
sync: false,
localVue,
});
});
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
it('should render user information', () => {
expect(vm.$el.querySelector('.user-avatar-link img').getAttribute('src')).toEqual(
note.author.avatar_url,
);
const { author } = note;
const avatar = wrapper.find(UserAvatarLink);
const avatarProps = avatar.props();
expect(avatarProps.linkHref).toBe(author.path);
expect(avatarProps.imgSrc).toBe(author.avatar_url);
expect(avatarProps.imgAlt).toBe(author.name);
expect(avatarProps.imgSize).toBe(40);
});
it('should render note header content', () => {
const el = vm.$el.querySelector('.note-header .note-header-author-name');
const noteHeader = wrapper.find(NoteHeader);
const noteHeaderProps = noteHeader.props();
expect(el.textContent.trim()).toEqual(note.author.name);
expect(noteHeaderProps.author).toEqual(note.author);
expect(noteHeaderProps.createdAt).toEqual(note.created_at);
expect(noteHeaderProps.noteId).toEqual(note.id);
});
it('should render note actions', () => {
expect(vm.$el.querySelector('.note-actions')).toBeDefined();
const { author } = note;
const noteActions = wrapper.find(NoteActions);
const noteActionsProps = noteActions.props();
expect(noteActionsProps.authorId).toBe(author.id);
expect(noteActionsProps.noteId).toBe(note.id);
expect(noteActionsProps.noteUrl).toBe(note.noteable_note_url);
expect(noteActionsProps.accessLevel).toBe(note.human_access);
expect(noteActionsProps.canEdit).toBe(note.current_user.can_edit);
expect(noteActionsProps.canAwardEmoji).toBe(note.current_user.can_award_emoji);
expect(noteActionsProps.canDelete).toBe(note.current_user.can_edit);
expect(noteActionsProps.canReportAsAbuse).toBe(true);
expect(noteActionsProps.canResolve).toBe(false);
expect(noteActionsProps.reportAbusePath).toBe(note.report_abuse_path);
expect(noteActionsProps.resolvable).toBe(false);
expect(noteActionsProps.isResolved).toBe(false);
expect(noteActionsProps.isResolving).toBe(false);
expect(noteActionsProps.resolvedBy).toEqual({});
});
it('should render issue body', () => {
expect(vm.$el.querySelector('.note-text').innerHTML).toEqual(note.note_html);
const noteBody = wrapper.find(NoteBody);
const noteBodyProps = noteBody.props();
expect(noteBodyProps.note).toEqual(note);
expect(noteBodyProps.line).toBe(null);
expect(noteBodyProps.canEdit).toBe(note.current_user.can_edit);
expect(noteBodyProps.isEditing).toBe(false);
expect(noteBodyProps.helpPagePath).toBe('');
});
it('prevents note preview xss', done => {
const imgSrc = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
const noteBody = `<img src="${imgSrc}" onload="alert(1)" />`;
const alertSpy = spyOn(window, 'alert');
vm.updateNote = () => new Promise($.noop);
store.hotUpdate({
actions: {
updateNote() {},
},
});
const noteBodyComponent = wrapper.find(NoteBody);
vm.formUpdateHandler(noteBody, null, $.noop);
noteBodyComponent.vm.$emit('handleFormUpdate', noteBody, null, () => {});
setTimeout(() => {
expect(alertSpy).not.toHaveBeenCalled();
expect(vm.note.note_html).toEqual(_.escape(noteBody));
expect(wrapper.vm.note.note_html).toEqual(_.escape(noteBody));
done();
}, 0);
});
......@@ -66,17 +107,23 @@ describe('issue_note', () => {
describe('cancel edit', () => {
it('restores content of updated note', done => {
const noteBody = 'updated note text';
vm.updateNote = () => Promise.resolve();
store.hotUpdate({
actions: {
updateNote() {},
},
});
const noteBodyComponent = wrapper.find(NoteBody);
noteBodyComponent.vm.resetAutoSave = () => {};
vm.formUpdateHandler(noteBody, null, $.noop);
noteBodyComponent.vm.$emit('handleFormUpdate', noteBody, null, () => {});
setTimeout(() => {
expect(vm.note.note_html).toEqual(noteBody);
expect(wrapper.vm.note.note_html).toEqual(noteBody);
vm.formCancelHandler();
noteBodyComponent.vm.$emit('cancelForm');
setTimeout(() => {
expect(vm.note.note_html).toEqual(noteBody);
expect(wrapper.vm.note.note_html).toEqual(noteBody);
done();
});
......
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();
});
});
});
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();
});
});
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);
});
});
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();
});
});
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',
};
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');
});
});
});
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