Commit d74fcc9b authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 8a7efa45
<script>
import {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlSearchBoxByType,
GlIcon,
} from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import { mapGetters } from 'vuex';
export default {
name: 'CiEnvironmentsDropdown',
components: {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlSearchBoxByType,
GlIcon,
},
props: {
value: {
type: String,
required: false,
default: '',
},
},
data() {
return {
searchTerm: this.value || '',
};
},
computed: {
...mapGetters(['joinedEnvironments']),
composedCreateButtonLabel() {
return sprintf(__('Create wildcard: %{searchTerm}'), { searchTerm: this.searchTerm });
},
shouldRenderCreateButton() {
return this.searchTerm && !this.joinedEnvironments.includes(this.searchTerm);
},
filteredResults() {
const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
return this.joinedEnvironments.filter(resultString =>
resultString.toLowerCase().includes(lowerCasedSearchTerm),
);
},
},
watch: {
value(newVal) {
this.searchTerm = newVal;
},
},
methods: {
selectEnvironment(selected) {
this.$emit('selectEnvironment', selected);
this.searchTerm = '';
},
createClicked() {
this.$emit('createClicked', this.searchTerm);
this.searchTerm = '';
},
isSelected(env) {
return this.value === env;
},
},
};
</script>
<template>
<gl-dropdown :text="value">
<gl-search-box-by-type v-model.trim="searchTerm" class="m-2" />
<gl-dropdown-item
v-for="environment in filteredResults"
:key="environment"
@click="selectEnvironment(environment)"
>
<gl-icon
:class="{ invisible: !isSelected(environment) }"
name="mobile-issue-close"
class="vertical-align-middle"
/>
{{ environment }}
</gl-dropdown-item>
<gl-dropdown-item v-if="!filteredResults.length" ref="noMatchingResults">{{
__('No matching results')
}}</gl-dropdown-item>
<template v-if="shouldRenderCreateButton">
<gl-dropdown-divider />
<gl-dropdown-item @click="createClicked">
{{ composedCreateButtonLabel }}
</gl-dropdown-item>
</template>
</gl-dropdown>
</template>
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { __ } from '~/locale'; import { __ } from '~/locale';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { ADD_CI_VARIABLE_MODAL_ID } from '../constants'; import { ADD_CI_VARIABLE_MODAL_ID } from '../constants';
import CiEnvironmentsDropdown from './ci_environments_dropdown.vue';
import { import {
GlButton, GlButton,
GlModal, GlModal,
...@@ -17,6 +18,7 @@ import { ...@@ -17,6 +18,7 @@ import {
export default { export default {
modalId: ADD_CI_VARIABLE_MODAL_ID, modalId: ADD_CI_VARIABLE_MODAL_ID,
components: { components: {
CiEnvironmentsDropdown,
GlButton, GlButton,
GlModal, GlModal,
GlFormSelect, GlFormSelect,
...@@ -36,6 +38,7 @@ export default { ...@@ -36,6 +38,7 @@ export default {
'variableBeingEdited', 'variableBeingEdited',
'isGroup', 'isGroup',
'maskableRegex', 'maskableRegex',
'selectedEnvironment',
]), ]),
canSubmit() { canSubmit() {
if (this.variableData.masked && this.maskedState === false) { if (this.variableData.masked && this.maskedState === false) {
...@@ -80,6 +83,10 @@ export default { ...@@ -80,6 +83,10 @@ export default {
'displayInputValue', 'displayInputValue',
'clearModal', 'clearModal',
'deleteVariable', 'deleteVariable',
'setEnvironmentScope',
'addWildCardScope',
'resetSelectedEnvironment',
'setSelectedEnvironment',
]), ]),
updateOrAddVariable() { updateOrAddVariable() {
if (this.variableBeingEdited) { if (this.variableBeingEdited) {
...@@ -95,6 +102,7 @@ export default { ...@@ -95,6 +102,7 @@ export default {
} else { } else {
this.clearModal(); this.clearModal();
} }
this.resetSelectedEnvironment();
}, },
hideModal() { hideModal() {
this.$refs.modal.hide(); this.$refs.modal.hide();
...@@ -158,10 +166,11 @@ export default { ...@@ -158,10 +166,11 @@ export default {
label-for="ci-variable-env" label-for="ci-variable-env"
class="w-50" class="w-50"
> >
<gl-form-select <ci-environments-dropdown
id="ci-variable-env" class="w-100"
v-model="variableData.environment_scope" :value="variableData.environment_scope"
:options="environments" @selectEnvironment="setEnvironmentScope"
@createClicked="addWildCardScope"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
......
...@@ -92,6 +92,7 @@ export default { ...@@ -92,6 +92,7 @@ export default {
sort-by="key" sort-by="key"
sort-direction="asc" sort-direction="asc"
stacked="lg" stacked="lg"
table-class="text-secondary"
fixed fixed
show-empty show-empty
sort-icon-left sort-icon-left
......
...@@ -6,7 +6,7 @@ export const ADD_CI_VARIABLE_MODAL_ID = 'add-ci-variable'; ...@@ -6,7 +6,7 @@ export const ADD_CI_VARIABLE_MODAL_ID = 'add-ci-variable';
export const displayText = { export const displayText = {
variableText: __('Var'), variableText: __('Var'),
fileText: __('File'), fileText: __('File'),
allEnvironmentsText: __('All'), allEnvironmentsText: __('All (default)'),
}; };
export const types = { export const types = {
......
...@@ -153,3 +153,22 @@ export const fetchEnvironments = ({ dispatch, state }) => { ...@@ -153,3 +153,22 @@ export const fetchEnvironments = ({ dispatch, state }) => {
createFlash(__('There was an error fetching the environments information.')); createFlash(__('There was an error fetching the environments information.'));
}); });
}; };
export const setEnvironmentScope = ({ commit, dispatch }, environment) => {
commit(types.SET_ENVIRONMENT_SCOPE, environment);
dispatch('setSelectedEnvironment', environment);
};
export const addWildCardScope = ({ commit, dispatch }, environment) => {
commit(types.ADD_WILD_CARD_SCOPE, environment);
commit(types.SET_ENVIRONMENT_SCOPE, environment);
dispatch('setSelectedEnvironment', environment);
};
export const resetSelectedEnvironment = ({ commit }) => {
commit(types.RESET_SELECTED_ENVIRONMENT);
};
export const setSelectedEnvironment = ({ commit }, environment) => {
commit(types.SET_SELECTED_ENVIRONMENT, environment);
};
/* eslint-disable import/prefer-default-export */
// Disabling import/prefer-default-export can be
// removed once a second getter is added to this file
import { uniq } from 'lodash';
export const joinedEnvironments = state => {
const scopesFromVariables = (state.variables || []).map(variable => variable.environment_scope);
return uniq(state.environments.concat(scopesFromVariables)).sort();
};
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import * as actions from './actions'; import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
import state from './state'; import state from './state';
...@@ -10,6 +11,7 @@ export default (initialState = {}) => ...@@ -10,6 +11,7 @@ export default (initialState = {}) =>
new Vuex.Store({ new Vuex.Store({
actions, actions,
mutations, mutations,
getters,
state: { state: {
...state(), ...state(),
...initialState, ...initialState,
......
...@@ -20,3 +20,7 @@ export const RECEIVE_UPDATE_VARIABLE_ERROR = 'RECEIVE_UPDATE_VARIABLE_ERROR'; ...@@ -20,3 +20,7 @@ export const RECEIVE_UPDATE_VARIABLE_ERROR = 'RECEIVE_UPDATE_VARIABLE_ERROR';
export const REQUEST_ENVIRONMENTS = 'REQUEST_ENVIRONMENTS'; export const REQUEST_ENVIRONMENTS = 'REQUEST_ENVIRONMENTS';
export const RECEIVE_ENVIRONMENTS_SUCCESS = 'RECEIVE_ENVIRONMENTS_SUCCESS'; export const RECEIVE_ENVIRONMENTS_SUCCESS = 'RECEIVE_ENVIRONMENTS_SUCCESS';
export const SET_ENVIRONMENT_SCOPE = 'SET_ENVIRONMENT_SCOPE';
export const ADD_WILD_CARD_SCOPE = 'ADD_WILD_CARD_SCOPE';
export const RESET_SELECTED_ENVIRONMENT = 'RESET_SELECTED_ENVIRONMENT';
export const SET_SELECTED_ENVIRONMENT = 'SET_SELECTED_ENVIRONMENT';
...@@ -83,4 +83,25 @@ export default { ...@@ -83,4 +83,25 @@ export default {
state.variableBeingEdited = null; state.variableBeingEdited = null;
state.showInputValue = false; state.showInputValue = false;
}, },
[types.SET_ENVIRONMENT_SCOPE](state, environment) {
if (state.variableBeingEdited) {
state.variableBeingEdited.environment_scope = environment;
} else {
state.variable.environment_scope = environment;
}
},
[types.ADD_WILD_CARD_SCOPE](state, environment) {
state.environments.push(environment);
state.environments.sort();
},
[types.RESET_SELECTED_ENVIRONMENT](state) {
state.selectedEnvironment = '';
},
[types.SET_SELECTED_ENVIRONMENT](state, environment) {
state.selectedEnvironment = environment;
},
}; };
...@@ -21,4 +21,5 @@ export default () => ({ ...@@ -21,4 +21,5 @@ export default () => ({
environments: [], environments: [],
typeOptions: [displayText.variableText, displayText.fileText], typeOptions: [displayText.variableText, displayText.fileText],
variableBeingEdited: null, variableBeingEdited: null,
selectedEnvironment: '',
}); });
/* eslint-disable */ /* eslint-disable */
import { template as _template } from 'underscore'; import { template as _template } from 'lodash';
import { DATA_TRIGGER, DATA_DROPDOWN, TEMPLATE_REGEX } from './constants'; import { DATA_TRIGGER, DATA_DROPDOWN, TEMPLATE_REGEX } from './constants';
const utils = { const utils = {
......
...@@ -101,7 +101,8 @@ export default { ...@@ -101,7 +101,8 @@ export default {
return this.graphData.title || ''; return this.graphData.title || '';
}, },
alertWidgetAvailable() { alertWidgetAvailable() {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData; // This method is extended by ee functionality
return false;
}, },
graphDataHasMetrics() { graphDataHasMetrics() {
return ( return (
...@@ -209,7 +210,7 @@ export default { ...@@ -209,7 +210,7 @@ export default {
> >
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<alert-widget <alert-widget
v-if="alertWidgetAvailable && graphData" v-if="alertWidgetAvailable"
:modal-id="`alert-modal-${index}`" :modal-id="`alert-modal-${index}`"
:alerts-endpoint="alertsEndpoint" :alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.metrics" :relevant-queries="graphData.metrics"
......
...@@ -104,3 +104,8 @@ export const endpointKeys = [ ...@@ -104,3 +104,8 @@ export const endpointKeys = [
* as Vue props. * as Vue props.
*/ */
export const initialStateKeys = [...endpointKeys, 'currentEnvironmentName']; export const initialStateKeys = [...endpointKeys, 'currentEnvironmentName'];
/**
* Constant to indicate if a metric exists in the database
*/
export const NOT_IN_DB_PREFIX = 'NO_DB';
...@@ -144,7 +144,7 @@ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => { ...@@ -144,7 +144,7 @@ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => {
const minStep = 60; const minStep = 60;
const queryDataPoints = 600; const queryDataPoints = 600;
const step = Math.max(minStep, Math.ceil(timeDiff / queryDataPoints)); const step = metric.step ? metric.step : Math.max(minStep, Math.ceil(timeDiff / queryDataPoints));
const queryParams = { const queryParams = {
start_time, start_time,
......
import { NOT_IN_DB_PREFIX } from '../constants';
const metricsIdsInPanel = panel => const metricsIdsInPanel = panel =>
panel.metrics.filter(metric => metric.metricId && metric.result).map(metric => metric.metricId); panel.metrics.filter(metric => metric.metricId && metric.result).map(metric => metric.metricId);
...@@ -58,6 +60,29 @@ export const metricsWithData = state => groupKey => { ...@@ -58,6 +60,29 @@ export const metricsWithData = state => groupKey => {
return res; return res;
}; };
/**
* Metrics loaded from project-defined dashboards do not have a metric_id.
* This getter checks which metrics are stored in the db (have a metric id)
* This is hopefully a temporary solution until BE processes metrics before passing to FE
*
* Related:
* https://gitlab.com/gitlab-org/gitlab/-/issues/28241
* https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27447
*/
export const metricsSavedToDb = state => {
const metricIds = [];
state.dashboard.panelGroups.forEach(({ panels }) => {
panels.forEach(({ metrics }) => {
const metricIdsInDb = metrics
.filter(({ metricId }) => !metricId.startsWith(NOT_IN_DB_PREFIX))
.map(({ metricId }) => metricId);
metricIds.push(...metricIdsInDb);
});
});
return metricIds;
};
/** /**
* Filter environments by names. * Filter environments by names.
* *
......
...@@ -2,6 +2,7 @@ import { slugify } from '~/lib/utils/text_utility'; ...@@ -2,6 +2,7 @@ import { slugify } from '~/lib/utils/text_utility';
import createGqClient, { fetchPolicies } from '~/lib/graphql'; import createGqClient, { fetchPolicies } from '~/lib/graphql';
import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format'; import { SUPPORTED_FORMATS } from '~/lib/utils/unit_format';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { NOT_IN_DB_PREFIX } from '../constants';
export const gqClient = createGqClient( export const gqClient = createGqClient(
{}, {},
...@@ -14,11 +15,18 @@ export const gqClient = createGqClient( ...@@ -14,11 +15,18 @@ export const gqClient = createGqClient(
* Metrics loaded from project-defined dashboards do not have a metric_id. * Metrics loaded from project-defined dashboards do not have a metric_id.
* This method creates a unique ID combining metric_id and id, if either is present. * This method creates a unique ID combining metric_id and id, if either is present.
* This is hopefully a temporary solution until BE processes metrics before passing to FE * This is hopefully a temporary solution until BE processes metrics before passing to FE
*
* Related:
* https://gitlab.com/gitlab-org/gitlab/-/issues/28241
* https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27447
*
* @param {Object} metric - metric * @param {Object} metric - metric
* @param {Number} metric.metric_id - Database metric id
* @param {String} metric.id - User-defined identifier
* @returns {Object} - normalized metric with a uniqueID * @returns {Object} - normalized metric with a uniqueID
*/ */
// eslint-disable-next-line babel/camelcase // eslint-disable-next-line babel/camelcase
export const uniqMetricsId = ({ metric_id, id }) => `${metric_id}_${id}`; export const uniqMetricsId = ({ metric_id, id }) => `${metric_id || NOT_IN_DB_PREFIX}_${id}`;
/** /**
* Project path has a leading slash that doesn't work well * Project path has a leading slash that doesn't work well
......
...@@ -15,7 +15,7 @@ class FileUploader < GitlabUploader ...@@ -15,7 +15,7 @@ class FileUploader < GitlabUploader
prepend ObjectStorage::Extension::RecordsUploads prepend ObjectStorage::Extension::RecordsUploads
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}.freeze MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}.freeze
DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\h{32})/(?<identifier>.*)}.freeze DYNAMIC_PATH_PATTERN = %r{.*/(?<secret>\h{10,32})/(?<identifier>.*)}.freeze
VALID_SECRET_PATTERN = %r{\A\h{10,32}\z}.freeze VALID_SECRET_PATTERN = %r{\A\h{10,32}\z}.freeze
InvalidSecret = Class.new(StandardError) InvalidSecret = Class.new(StandardError)
......
...@@ -66,3 +66,34 @@ ...@@ -66,3 +66,34 @@
= _("Upload a private key for your certificate") = _("Upload a private key for your certificate")
= f.submit @domain.persisted? ? _('Save changes') : _('Add domain'), class: "btn btn-success js-serverless-domain-submit", disabled: @domain.persisted? = f.submit @domain.persisted? ? _('Save changes') : _('Add domain'), class: "btn btn-success js-serverless-domain-submit", disabled: @domain.persisted?
- if @domain.persisted?
%button.btn.btn-remove{ type: 'button', data: { toggle: 'modal', target: "#modal-delete-domain" } }
= _('Delete domain')
-# haml-lint:disable NoPlainNodes
- if @domain.persisted?
- domain_attached = @domain.serverless_domain_clusters.count > 0
.modal{ id: "modal-delete-domain", tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
%h3.page-title= _('Delete serverless domain?')
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
.modal-body
- if domain_attached
= _("You must disassociate %{domain} from all clusters it is attached to before deletion.").html_safe % { domain: "<code>#{@domain.domain}</code>".html_safe }
- else
= _("You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application.").html_safe % { domain: "<code>#{@domain.domain}</code>".html_safe }
.modal-footer
%a{ href: '#', data: { dismiss: 'modal' }, class: 'btn btn-default' }
= _('Cancel')
= link_to _('Delete domain'),
admin_serverless_domain_path(@domain.id),
title: _('Delete'),
method: :delete,
class: "btn btn-remove",
disabled: domain_attached
---
title: Fix incorrect regex used in FileUploader#extract_dynamic_path
merge_request: 28683
author:
type: fixed
---
title: Allow defining of metric step in dashboard yml
merge_request: 28247
author:
type: added
...@@ -1631,6 +1631,9 @@ msgstr "" ...@@ -1631,6 +1631,9 @@ msgstr ""
msgid "All %{replicableType} are being scheduled for %{action}" msgid "All %{replicableType} are being scheduled for %{action}"
msgstr "" msgstr ""
msgid "All (default)"
msgstr ""
msgid "All Members" msgid "All Members"
msgstr "" msgstr ""
...@@ -5904,6 +5907,9 @@ msgstr "" ...@@ -5904,6 +5907,9 @@ msgstr ""
msgid "Create project label" msgid "Create project label"
msgstr "" msgstr ""
msgid "Create wildcard: %{searchTerm}"
msgstr ""
msgid "Create your first page" msgid "Create your first page"
msgstr "" msgstr ""
...@@ -6446,6 +6452,9 @@ msgstr "" ...@@ -6446,6 +6452,9 @@ msgstr ""
msgid "Delete comment" msgid "Delete comment"
msgstr "" msgstr ""
msgid "Delete domain"
msgstr ""
msgid "Delete license" msgid "Delete license"
msgstr "" msgstr ""
...@@ -6458,6 +6467,9 @@ msgstr "" ...@@ -6458,6 +6467,9 @@ msgstr ""
msgid "Delete project" msgid "Delete project"
msgstr "" msgstr ""
msgid "Delete serverless domain?"
msgstr ""
msgid "Delete snippet" msgid "Delete snippet"
msgstr "" msgstr ""
...@@ -23079,6 +23091,9 @@ msgstr "" ...@@ -23079,6 +23091,9 @@ msgstr ""
msgid "You" msgid "You"
msgstr "" msgstr ""
msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
msgstr ""
msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete." msgid "You are about to transfer the control of your account to %{group_name} group. This action is NOT reversible, you won't be able to access any of your groups and projects outside of %{group_name} once this transfer is complete."
msgstr "" msgstr ""
...@@ -23355,6 +23370,9 @@ msgstr "" ...@@ -23355,6 +23370,9 @@ msgstr ""
msgid "You must accept our Terms of Service and privacy policy in order to register an account" msgid "You must accept our Terms of Service and privacy policy in order to register an account"
msgstr "" msgstr ""
msgid "You must disassociate %{domain} from all clusters it is attached to before deletion."
msgstr ""
msgid "You must have maintainer access to force delete a lock" msgid "You must have maintainer access to force delete a lock"
msgstr "" msgstr ""
......
...@@ -56,4 +56,32 @@ describe 'Admin Serverless Domains', :js do ...@@ -56,4 +56,32 @@ describe 'Admin Serverless Domains', :js do
expect(page).to have_content 'Domain was successfully updated' expect(page).to have_content 'Domain was successfully updated'
expect(page).to have_content '/CN=test-certificate' expect(page).to have_content '/CN=test-certificate'
end end
context 'when domain exists' do
let!(:domain) { create(:pages_domain, :instance_serverless) }
it 'Displays a modal when attempting to delete a domain' do
visit admin_serverless_domains_path
click_button 'Delete domain'
page.within '#modal-delete-domain' do
expect(page).to have_content "You are about to delete #{domain.domain} from your instance."
expect(page).to have_link('Delete domain')
end
end
it 'Displays a modal with disabled button if unable to delete a domain' do
create(:serverless_domain_cluster, pages_domain: domain)
visit admin_serverless_domains_path
click_button 'Delete domain'
page.within '#modal-delete-domain' do
expect(page).to have_content "You must disassociate #{domain.domain} from all clusters it is attached to before deletion."
expect(page).to have_link('Delete domain')
end
end
end
end end
import Vuex from 'vuex';
import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlDropdownItem, GlIcon } from '@gitlab/ui';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Ci environments dropdown', () => {
let wrapper;
let store;
const createComponent = term => {
store = new Vuex.Store({
getters: {
joinedEnvironments: () => ['dev', 'prod', 'staging'],
},
});
wrapper = shallowMount(CiEnvironmentsDropdown, {
store,
localVue,
propsData: {
value: term,
},
});
};
const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem);
const findDropdownItemByIndex = index => wrapper.findAll(GlDropdownItem).at(index);
const findActiveIconByIndex = index => wrapper.findAll(GlIcon).at(index);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('No enviroments found', () => {
beforeEach(() => {
createComponent('stable');
});
it('renders create button with search term if enviroments do not contain search term', () => {
expect(findAllDropdownItems()).toHaveLength(2);
expect(findDropdownItemByIndex(1).text()).toBe('Create wildcard: stable');
});
it('renders empty results message', () => {
expect(findDropdownItemByIndex(0).text()).toBe('No matching results');
});
});
describe('Search term is empty', () => {
beforeEach(() => {
createComponent('');
});
it('renders all enviroments when search term is empty', () => {
expect(findAllDropdownItems()).toHaveLength(3);
expect(findDropdownItemByIndex(0).text()).toBe('dev');
expect(findDropdownItemByIndex(1).text()).toBe('prod');
expect(findDropdownItemByIndex(2).text()).toBe('staging');
});
});
describe('Enviroments found', () => {
beforeEach(() => {
createComponent('prod');
});
it('renders only the enviroment searched for', () => {
expect(findAllDropdownItems()).toHaveLength(1);
expect(findDropdownItemByIndex(0).text()).toBe('prod');
});
it('should not display create button', () => {
const enviroments = findAllDropdownItems().filter(env => env.text().startsWith('Create'));
expect(enviroments).toHaveLength(0);
expect(findAllDropdownItems()).toHaveLength(1);
});
it('should not display empty results message', () => {
expect(wrapper.find({ ref: 'noMatchingResults' }).exists()).toBe(false);
});
it('should display active checkmark if active', () => {
expect(findActiveIconByIndex(0).classes('invisible')).toBe(false);
});
describe('Custom events', () => {
it('should emit selectEnvironment if an environment is clicked', () => {
findDropdownItemByIndex(0).vm.$emit('click');
expect(wrapper.emitted('selectEnvironment')).toEqual([['prod']]);
});
it('should emit createClicked if an environment is clicked', () => {
createComponent('newscope');
findDropdownItemByIndex(1).vm.$emit('click');
expect(wrapper.emitted('createClicked')).toEqual([['newscope']]);
});
});
});
});
export default { export default {
mockVariables: [ mockVariables: [
{ {
environment_scope: 'All environments', environment_scope: 'All (default)',
id: 113, id: 113,
key: 'test_var', key: 'test_var',
masked: false, masked: false,
...@@ -37,7 +37,7 @@ export default { ...@@ -37,7 +37,7 @@ export default {
mockVariablesDisplay: [ mockVariablesDisplay: [
{ {
environment_scope: 'All', environment_scope: 'All (default)',
id: 113, id: 113,
key: 'test_var', key: 'test_var',
masked: false, masked: false,
...@@ -47,7 +47,7 @@ export default { ...@@ -47,7 +47,7 @@ export default {
variable_type: 'Var', variable_type: 'Var',
}, },
{ {
environment_scope: 'All', environment_scope: 'All (default)',
id: 114, id: 114,
key: 'test_var_2', key: 'test_var_2',
masked: false, masked: false,
...@@ -88,4 +88,67 @@ export default { ...@@ -88,4 +88,67 @@ export default {
ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ
98TwDIK/39WEB/V607As+KoYazQG8drorw== 98TwDIK/39WEB/V607As+KoYazQG8drorw==
-----END CERTIFICATE REQUEST-----`, -----END CERTIFICATE REQUEST-----`,
mockVariableScopes: [
{
id: 13,
key: 'test_var_1',
value: 'test_val_1',
variable_type: 'File',
protected: true,
masked: true,
environment_scope: 'All (default)',
secret_value: 'test_val_1',
},
{
id: 28,
key: 'goku_var',
value: 'goku_val',
variable_type: 'Var',
protected: true,
masked: true,
environment_scope: 'staging',
secret_value: 'goku_val',
},
{
id: 25,
key: 'test_var_4',
value: 'test_val_4',
variable_type: 'Var',
protected: false,
masked: false,
environment_scope: 'production',
secret_value: 'test_val_4',
},
{
id: 14,
key: 'test_var_2',
value: 'test_val_2',
variable_type: 'File',
protected: false,
masked: false,
environment_scope: 'staging',
secret_value: 'test_val_2',
},
{
id: 24,
key: 'test_var_3',
value: 'test_val_3',
variable_type: 'Var',
protected: false,
masked: false,
environment_scope: 'All (default)',
secret_value: 'test_val_3',
},
{
id: 26,
key: 'test_var_5',
value: 'test_val_5',
variable_type: 'Var',
protected: false,
masked: false,
environment_scope: 'production',
secret_value: 'test_val_5',
},
],
}; };
import * as getters from '~/ci_variable_list/store/getters';
import mockData from '../services/mock_data';
describe('Ci variable getters', () => {
describe('joinedEnvironments', () => {
it('should join fetched enviroments with variable environment scopes', () => {
const state = {
environments: ['All (default)', 'staging', 'deployment', 'prod'],
variables: mockData.mockVariableScopes,
};
expect(getters.joinedEnvironments(state)).toEqual([
'All (default)',
'deployment',
'prod',
'production',
'staging',
]);
});
});
});
...@@ -4,6 +4,15 @@ import * as types from '~/ci_variable_list/store/mutation_types'; ...@@ -4,6 +4,15 @@ import * as types from '~/ci_variable_list/store/mutation_types';
describe('CI variable list mutations', () => { describe('CI variable list mutations', () => {
let stateCopy; let stateCopy;
const variableBeingEdited = {
environment_scope: '*',
id: 63,
key: 'test_var',
masked: false,
protected: false,
value: 'test_val',
variable_type: 'env_var',
};
beforeEach(() => { beforeEach(() => {
stateCopy = state(); stateCopy = state();
...@@ -21,16 +30,6 @@ describe('CI variable list mutations', () => { ...@@ -21,16 +30,6 @@ describe('CI variable list mutations', () => {
describe('VARIABLE_BEING_EDITED', () => { describe('VARIABLE_BEING_EDITED', () => {
it('should set variable that is being edited', () => { it('should set variable that is being edited', () => {
const variableBeingEdited = {
environment_scope: '*',
id: 63,
key: 'test_var',
masked: false,
protected: false,
value: 'test_val',
variable_type: 'env_var',
};
mutations[types.VARIABLE_BEING_EDITED](stateCopy, variableBeingEdited); mutations[types.VARIABLE_BEING_EDITED](stateCopy, variableBeingEdited);
expect(stateCopy.variableBeingEdited).toEqual(variableBeingEdited); expect(stateCopy.variableBeingEdited).toEqual(variableBeingEdited);
...@@ -53,7 +52,7 @@ describe('CI variable list mutations', () => { ...@@ -53,7 +52,7 @@ describe('CI variable list mutations', () => {
secret_value: '', secret_value: '',
protected: false, protected: false,
masked: false, masked: false,
environment_scope: 'All', environment_scope: 'All (default)',
}; };
mutations[types.CLEAR_MODAL](stateCopy); mutations[types.CLEAR_MODAL](stateCopy);
...@@ -61,4 +60,41 @@ describe('CI variable list mutations', () => { ...@@ -61,4 +60,41 @@ describe('CI variable list mutations', () => {
expect(stateCopy.variable).toEqual(modalState); expect(stateCopy.variable).toEqual(modalState);
}); });
}); });
describe('RECEIVE_ENVIRONMENTS_SUCCESS', () => {
it('should set environments', () => {
const environments = ['env1', 'env2'];
mutations[types.RECEIVE_ENVIRONMENTS_SUCCESS](stateCopy, environments);
expect(stateCopy.environments).toEqual(['All (default)', 'env1', 'env2']);
});
});
describe('SET_ENVIRONMENT_SCOPE', () => {
const environment = 'production';
it('should set scope to variable being updated if updating variable', () => {
stateCopy.variableBeingEdited = variableBeingEdited;
mutations[types.SET_ENVIRONMENT_SCOPE](stateCopy, environment);
expect(stateCopy.variableBeingEdited.environment_scope).toBe('production');
});
it('should set scope to variable if adding new variable', () => {
mutations[types.SET_ENVIRONMENT_SCOPE](stateCopy, environment);
expect(stateCopy.variable.environment_scope).toBe('production');
});
});
describe('ADD_WILD_CARD_SCOPE', () => {
it('should add wild card scope to enviroments array and sort', () => {
stateCopy.environments = ['dev', 'staging'];
mutations[types.ADD_WILD_CARD_SCOPE](stateCopy, 'production');
expect(stateCopy.environments).toEqual(['dev', 'production', 'staging']);
});
});
}); });
...@@ -288,7 +288,7 @@ export const mockedEmptyResult = { ...@@ -288,7 +288,7 @@ export const mockedEmptyResult = {
}; };
export const mockedEmptyThroughputResult = { export const mockedEmptyThroughputResult = {
metricId: 'undefined_response_metrics_nginx_ingress_16_throughput_status_code', metricId: 'NO_DB_response_metrics_nginx_ingress_16_throughput_status_code',
result: [], result: [],
}; };
...@@ -304,12 +304,12 @@ export const mockedQueryResultPayloadCoresTotal = { ...@@ -304,12 +304,12 @@ export const mockedQueryResultPayloadCoresTotal = {
export const mockedQueryResultFixture = { export const mockedQueryResultFixture = {
// First metric in fixture `metrics_dashboard/environment_metrics_dashboard.json` // First metric in fixture `metrics_dashboard/environment_metrics_dashboard.json`
metricId: 'undefined_response_metrics_nginx_ingress_throughput_status_code', metricId: 'NO_DB_response_metrics_nginx_ingress_throughput_status_code',
result: metricsResult, result: metricsResult,
}; };
export const mockedQueryResultFixtureStatusCode = { export const mockedQueryResultFixtureStatusCode = {
metricId: 'undefined_response_metrics_nginx_ingress_latency_pod_average', metricId: 'NO_DB_response_metrics_nginx_ingress_latency_pod_average',
result: metricsResult, result: metricsResult,
}; };
...@@ -560,13 +560,11 @@ export const graphDataPrometheusQueryRange = { ...@@ -560,13 +560,11 @@ export const graphDataPrometheusQueryRange = {
weight: 2, weight: 2,
metrics: [ metrics: [
{ {
id: 'metric_a1', metricId: '2_metric_a',
metricId: '2',
query_range: query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
unit: 'MB', unit: 'MB',
label: 'Total Consumption', label: 'Total Consumption',
metric_id: 2,
prometheus_endpoint_path: prometheus_endpoint_path:
'/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024', '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
result: [ result: [
...@@ -587,13 +585,12 @@ export const graphDataPrometheusQueryRangeMultiTrack = { ...@@ -587,13 +585,12 @@ export const graphDataPrometheusQueryRangeMultiTrack = {
y_label: 'Time', y_label: 'Time',
metrics: [ metrics: [
{ {
metricId: '1', metricId: '1_metric_b',
id: 'response_metrics_nginx_ingress_throughput_status_code', id: 'response_metrics_nginx_ingress_throughput_status_code',
query_range: query_range:
'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)', 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)',
unit: 'req / sec', unit: 'req / sec',
label: 'Status Code', label: 'Status Code',
metric_id: 1,
prometheus_endpoint_path: prometheus_endpoint_path:
'/root/rails_nodb/environments/3/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29', '/root/rails_nodb/environments/3/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29',
result: [ result: [
...@@ -669,8 +666,7 @@ export const stackedColumnMockedData = { ...@@ -669,8 +666,7 @@ export const stackedColumnMockedData = {
series_name: 'group 1', series_name: 'group 1',
prometheus_endpoint_path: prometheus_endpoint_path:
'/root/autodevops-deploy-6/-/environments/24/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024', '/root/autodevops-deploy-6/-/environments/24/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024',
metric_id: 'undefined_metric_of_ages_1024', metricId: 'NO_DB_metric_of_ages_1024',
metricId: 'undefined_metric_of_ages_1024',
result: [ result: [
{ {
metric: {}, metric: {},
...@@ -688,8 +684,7 @@ export const stackedColumnMockedData = { ...@@ -688,8 +684,7 @@ export const stackedColumnMockedData = {
series_name: 'group 2', series_name: 'group 2',
prometheus_endpoint_path: prometheus_endpoint_path:
'/root/autodevops-deploy-6/-/environments/24/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024', '/root/autodevops-deploy-6/-/environments/24/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024',
metric_id: 'undefined_metric_of_ages_1000', metricId: 'NO_DB_metric_of_ages_1000',
metricId: 'undefined_metric_of_ages_1000',
result: [ result: [
{ {
metric: {}, metric: {},
...@@ -713,8 +708,7 @@ export const barMockData = { ...@@ -713,8 +708,7 @@ export const barMockData = {
{ {
id: 'sla_trends_primary_services', id: 'sla_trends_primary_services',
series_name: 'group 1', series_name: 'group 1',
metric_id: 'undefined_sla_trends_primary_services', metricId: 'NO_DB_sla_trends_primary_services',
metricId: 'undefined_sla_trends_primary_services',
query_range: query_range:
'avg(avg_over_time(slo_observation_status{environment="gprd", stage=~"main|", type=~"api|web|git|registry|sidekiq|ci-runners"}[1d])) by (type)', 'avg(avg_over_time(slo_observation_status{environment="gprd", stage=~"main|", type=~"api|web|git|registry|sidekiq|ci-runners"}[1d])) by (type)',
unit: 'Percentile', unit: 'Percentile',
......
...@@ -557,6 +557,86 @@ describe('Monitoring store actions', () => { ...@@ -557,6 +557,86 @@ describe('Monitoring store actions', () => {
).catch(done.fail); ).catch(done.fail);
}); });
describe('without metric defined step', () => {
const expectedParams = {
start_time: '2019-08-06T12:40:02.184Z',
end_time: '2019-08-06T20:40:02.184Z',
step: 60,
};
it('uses calculated step', done => {
mock.onGet('http://test').reply(200, { data }); // One attempt
testAction(
fetchPrometheusMetric,
{ metric, params },
state,
[
{
type: types.REQUEST_METRIC_RESULT,
payload: {
metricId: metric.metricId,
},
},
{
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
result: data.result,
},
},
],
[],
() => {
expect(mock.history.get[0].params).toEqual(expectedParams);
done();
},
).catch(done.fail);
});
});
describe('with metric defined step', () => {
beforeEach(() => {
metric.step = 7;
});
const expectedParams = {
start_time: '2019-08-06T12:40:02.184Z',
end_time: '2019-08-06T20:40:02.184Z',
step: 7,
};
it('uses metric step', done => {
mock.onGet('http://test').reply(200, { data }); // One attempt
testAction(
fetchPrometheusMetric,
{ metric, params },
state,
[
{
type: types.REQUEST_METRIC_RESULT,
payload: {
metricId: metric.metricId,
},
},
{
type: types.RECEIVE_METRIC_RESULT_SUCCESS,
payload: {
metricId: metric.metricId,
result: data.result,
},
},
],
[],
() => {
expect(mock.history.get[0].params).toEqual(expectedParams);
done();
},
).catch(done.fail);
});
});
it('commits result, when waiting for results', done => { it('commits result, when waiting for results', done => {
// Mock multiple attempts while the cache is filling up // Mock multiple attempts while the cache is filling up
mock.onGet('http://test').replyOnce(statusCodes.NO_CONTENT); mock.onGet('http://test').replyOnce(statusCodes.NO_CONTENT);
......
import _ from 'lodash';
import * as getters from '~/monitoring/stores/getters'; import * as getters from '~/monitoring/stores/getters';
import mutations from '~/monitoring/stores/mutations'; import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types'; import * as types from '~/monitoring/stores/mutation_types';
...@@ -274,4 +275,56 @@ describe('Monitoring store Getters', () => { ...@@ -274,4 +275,56 @@ describe('Monitoring store Getters', () => {
}); });
}); });
}); });
describe('metricsSavedToDb', () => {
let metricsSavedToDb;
let state;
let mockData;
beforeEach(() => {
mockData = _.cloneDeep(metricsDashboardPayload);
state = {
dashboard: {
panelGroups: [],
},
};
});
it('return no metrics when dashboard is not persisted', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, mockData);
metricsSavedToDb = getters.metricsSavedToDb(state);
expect(metricsSavedToDb).toEqual([]);
});
it('return a metric id when one metric is persisted', () => {
const id = 99;
const [metric] = mockData.panel_groups[0].panels[0].metrics;
metric.metric_id = id;
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, mockData);
metricsSavedToDb = getters.metricsSavedToDb(state);
expect(metricsSavedToDb).toEqual([`${id}_${metric.id}`]);
});
it('return a metric id when two metrics are persisted', () => {
const id1 = 101;
const id2 = 102;
const [metric1] = mockData.panel_groups[0].panels[0].metrics;
const [metric2] = mockData.panel_groups[0].panels[1].metrics;
// database persisted 2 metrics
metric1.metric_id = id1;
metric2.metric_id = id2;
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, mockData);
metricsSavedToDb = getters.metricsSavedToDb(state);
expect(metricsSavedToDb).toEqual([`${id1}_${metric1.id}`, `${id2}_${metric2.id}`]);
});
});
}); });
...@@ -66,13 +66,13 @@ describe('Monitoring mutations', () => { ...@@ -66,13 +66,13 @@ describe('Monitoring mutations', () => {
const groups = getGroups(); const groups = getGroups();
expect(groups[0].panels[0].metrics[0].metricId).toEqual( expect(groups[0].panels[0].metrics[0].metricId).toEqual(
'undefined_system_metrics_kubernetes_container_memory_total', 'NO_DB_system_metrics_kubernetes_container_memory_total',
); );
expect(groups[1].panels[0].metrics[0].metricId).toEqual( expect(groups[1].panels[0].metrics[0].metricId).toEqual(
'undefined_response_metrics_nginx_ingress_throughput_status_code', 'NO_DB_response_metrics_nginx_ingress_throughput_status_code',
); );
expect(groups[2].panels[0].metrics[0].metricId).toEqual( expect(groups[2].panels[0].metrics[0].metricId).toEqual(
'undefined_response_metrics_nginx_ingress_16_throughput_status_code', 'NO_DB_response_metrics_nginx_ingress_16_throughput_status_code',
); );
}); });
}); });
...@@ -184,7 +184,7 @@ describe('Monitoring mutations', () => { ...@@ -184,7 +184,7 @@ describe('Monitoring mutations', () => {
}); });
describe('Individual panel/metric results', () => { describe('Individual panel/metric results', () => {
const metricId = 'undefined_response_metrics_nginx_ingress_throughput_status_code'; const metricId = 'NO_DB_response_metrics_nginx_ingress_throughput_status_code';
const result = [ const result = [
{ {
values: [[0, 1], [1, 1], [1, 3]], values: [[0, 1], [1, 1], [1, 3]],
......
...@@ -307,7 +307,7 @@ describe('mapToDashboardViewModel', () => { ...@@ -307,7 +307,7 @@ describe('mapToDashboardViewModel', () => {
describe('uniqMetricsId', () => { describe('uniqMetricsId', () => {
[ [
{ input: { id: 1 }, expected: 'undefined_1' }, { input: { id: 1 }, expected: 'NO_DB_1' },
{ input: { metric_id: 2 }, expected: '2_undefined' }, { input: { metric_id: 2 }, expected: '2_undefined' },
{ input: { metric_id: 2, id: 21 }, expected: '2_21' }, { input: { metric_id: 2, id: 21 }, expected: '2_21' },
{ input: { metric_id: 22, id: 1 }, expected: '22_1' }, { input: { metric_id: 22, id: 1 }, expected: '22_1' },
......
...@@ -145,11 +145,39 @@ describe FileUploader do ...@@ -145,11 +145,39 @@ describe FileUploader do
end end
describe '.extract_dynamic_path' do describe '.extract_dynamic_path' do
it 'works with hashed storage' do context 'with a 32-byte hexadecimal secret in the path' do
path = 'export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/72a497a02fe3ee09edae2ed06d390038/dummy.txt' let(:secret) { SecureRandom.hex }
let(:path) { "export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/#{secret}/dummy.txt" }
expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt') it 'extracts the secret' do
expect(described_class.extract_dynamic_path(path)[:secret]).to eq('72a497a02fe3ee09edae2ed06d390038') expect(described_class.extract_dynamic_path(path)[:secret]).to eq(secret)
end
it 'extracts the identifier' do
expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt')
end
end
context 'with a 10-byte hexadecimal secret in the path' do
let(:secret) { SecureRandom.hex(10) }
let(:path) { "export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/#{secret}/dummy.txt" }
it 'extracts the secret' do
expect(described_class.extract_dynamic_path(path)[:secret]).to eq(secret)
end
it 'extracts the identifier' do
expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt')
end
end
context 'with an invalid secret in the path' do
let(:secret) { 'foo' }
let(:path) { "export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/#{secret}/dummy.txt" }
it 'returns nil' do
expect(described_class.extract_dynamic_path(path)).to be_nil
end
end end
end end
......
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