Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8f764d21b0
commit
8723197387
|
@ -30,6 +30,11 @@ rules:
|
|||
no-else-return:
|
||||
- error
|
||||
- allowElseIf: true
|
||||
import/no-unresolved:
|
||||
- error
|
||||
- ignore:
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/38226
|
||||
- '^ee_component/'
|
||||
import/no-useless-path-segments: off
|
||||
import/order: off
|
||||
lines-between-class-members: off
|
||||
|
|
|
@ -748,7 +748,7 @@ GEM
|
|||
pry-rails (0.3.6)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.1.1)
|
||||
puma (4.3.0)
|
||||
puma (4.3.1)
|
||||
nio4r (~> 2.0)
|
||||
puma_worker_killer (0.1.1)
|
||||
get_process_mem (~> 0.2)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import initSettingsPanels from '~/settings_panels';
|
||||
import SecretValues from '~/behaviors/secret_values';
|
||||
import AjaxVariableList from '~/ci_variable_list/ajax_variable_list';
|
||||
import registrySettingsApp from '~/registry/settings/registry_settings_bundle';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize expandable settings panels
|
||||
|
@ -32,4 +33,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
if (instanceDefaultBadge) instanceDefaultBadge.style.display = 'none';
|
||||
autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
|
||||
});
|
||||
|
||||
registrySettingsApp();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
computed: {
|
||||
...mapState({
|
||||
helpPagePath: 'helpPagePath',
|
||||
}),
|
||||
|
||||
helpText() {
|
||||
return sprintf(
|
||||
s__(
|
||||
'PackageRegistry|Read more about the %{helpLinkStart}Container Registry tag retention policies%{helpLinkEnd}',
|
||||
),
|
||||
{
|
||||
helpLinkStart: `<a href="${this.helpPagePath}" target="_blank">`,
|
||||
helpLinkEnd: '</a>',
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>
|
||||
{{ s__('PackageRegistry|Tag retention policies are designed to:') }}
|
||||
</p>
|
||||
<ul>
|
||||
<li>{{ s__('PackageRegistry|Keep and protect the images that matter most.') }}</li>
|
||||
<li>
|
||||
{{
|
||||
s__("PackageRegistry|Automatically remove extra images that aren't designed to be kept.")
|
||||
}}
|
||||
</li>
|
||||
</ul>
|
||||
<p ref="help-link" v-html="helpText"></p>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,24 @@
|
|||
import Vue from 'vue';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import store from './stores/';
|
||||
import RegistrySettingsApp from './components/registry_settings_app.vue';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
export default () => {
|
||||
const el = document.getElementById('js-registry-settings');
|
||||
if (!el) {
|
||||
return null;
|
||||
}
|
||||
store.dispatch('setInitialState', el.dataset);
|
||||
return new Vue({
|
||||
el,
|
||||
store,
|
||||
components: {
|
||||
RegistrySettingsApp,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('registry-settings-app', {});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
import * as types from './mutation_types';
|
||||
|
||||
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
|
||||
|
||||
// to avoid eslint error until more actions are added to the store
|
||||
export default () => {};
|
|
@ -0,0 +1,16 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import * as actions from './actions';
|
||||
import mutations from './mutations';
|
||||
import state from './state';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export const createStore = () =>
|
||||
new Vuex.Store({
|
||||
state,
|
||||
actions,
|
||||
mutations,
|
||||
});
|
||||
|
||||
export default createStore();
|
|
@ -0,0 +1,4 @@
|
|||
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
|
||||
|
||||
// to avoid eslint error until more actions are added to the store
|
||||
export default () => {};
|
|
@ -0,0 +1,8 @@
|
|||
import * as types from './mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SET_INITIAL_STATE](state, initialState) {
|
||||
state.helpPagePath = initialState.helpPagePath;
|
||||
state.registrySettingsEndpoint = initialState.registrySettingsEndpoint;
|
||||
},
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
export default () => ({
|
||||
/*
|
||||
* Help page path to generate the link
|
||||
*/
|
||||
helpPagePath: '',
|
||||
/*
|
||||
* Settings endpoint to call to fetch and update the settings
|
||||
*/
|
||||
registrySettingsEndpoint: '',
|
||||
});
|
|
@ -1,245 +0,0 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue';
|
||||
import { __ } from '~/locale';
|
||||
import timeagoMixin from '../../vue_shared/mixins/timeago';
|
||||
import LoadingButton from '../../vue_shared/components/loading_button.vue';
|
||||
import { visitUrl } from '../../lib/utils/url_utility';
|
||||
import createFlash from '../../flash';
|
||||
import MemoryUsage from './memory_usage.vue';
|
||||
import StatusIcon from './mr_widget_status_icon.vue';
|
||||
import ReviewAppLink from './review_app_link.vue';
|
||||
import MRWidgetService from '../services/mr_widget_service';
|
||||
|
||||
export default {
|
||||
// name: 'Deployment' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives
|
||||
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
|
||||
name: 'Deployment',
|
||||
components: {
|
||||
LoadingButton,
|
||||
MemoryUsage,
|
||||
StatusIcon,
|
||||
Icon,
|
||||
TooltipOnTruncate,
|
||||
FilteredSearchDropdown,
|
||||
ReviewAppLink,
|
||||
VisualReviewAppLink: () =>
|
||||
import('ee_component/vue_merge_request_widget/components/visual_review_app_link.vue'),
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
deployment: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
showMetrics: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
showVisualReviewApp: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
visualReviewAppMeta: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({
|
||||
sourceProjectId: '',
|
||||
sourceProjectPath: '',
|
||||
mergeRequestId: '',
|
||||
appUrl: '',
|
||||
}),
|
||||
},
|
||||
},
|
||||
deployedTextMap: {
|
||||
running: __('Deploying to'),
|
||||
success: __('Deployed to'),
|
||||
failed: __('Failed to deploy to'),
|
||||
created: __('Will deploy to'),
|
||||
canceled: __('Failed to deploy to'),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isStopping: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
deployTimeago() {
|
||||
return this.timeFormated(this.deployment.deployed_at);
|
||||
},
|
||||
deploymentExternalUrl() {
|
||||
if (this.deployment.changes && this.deployment.changes.length === 1) {
|
||||
return this.deployment.changes[0].external_url;
|
||||
}
|
||||
return this.deployment.external_url;
|
||||
},
|
||||
hasExternalUrls() {
|
||||
return Boolean(this.deployment.external_url && this.deployment.external_url_formatted);
|
||||
},
|
||||
hasDeploymentTime() {
|
||||
return Boolean(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
|
||||
},
|
||||
hasDeploymentMeta() {
|
||||
return Boolean(this.deployment.url && this.deployment.name);
|
||||
},
|
||||
hasMetrics() {
|
||||
return Boolean(this.deployment.metrics_url);
|
||||
},
|
||||
deployedText() {
|
||||
return this.$options.deployedTextMap[this.deployment.status];
|
||||
},
|
||||
isDeployInProgress() {
|
||||
return this.deployment.status === 'running';
|
||||
},
|
||||
deployInProgressTooltip() {
|
||||
return this.isDeployInProgress
|
||||
? __('Stopping this environment is currently not possible as a deployment is in progress')
|
||||
: '';
|
||||
},
|
||||
shouldRenderDropdown() {
|
||||
return this.deployment.changes && this.deployment.changes.length > 1;
|
||||
},
|
||||
showMemoryUsage() {
|
||||
return this.hasMetrics && this.showMetrics;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
stopEnvironment() {
|
||||
const msg = __('Are you sure you want to stop this environment?');
|
||||
const isConfirmed = confirm(msg); // eslint-disable-line
|
||||
|
||||
if (isConfirmed) {
|
||||
this.isStopping = true;
|
||||
|
||||
MRWidgetService.stopEnvironment(this.deployment.stop_url)
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
if (data.redirect_url) {
|
||||
visitUrl(data.redirect_url);
|
||||
}
|
||||
|
||||
this.isStopping = false;
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash(
|
||||
__('Something went wrong while stopping this environment. Please try again.'),
|
||||
);
|
||||
this.isStopping = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="deploy-heading">
|
||||
<div class="ci-widget media">
|
||||
<div class="media-body">
|
||||
<div class="deploy-body">
|
||||
<div class="js-deployment-info deployment-info">
|
||||
<template v-if="hasDeploymentMeta">
|
||||
<span> {{ deployedText }} </span>
|
||||
<tooltip-on-truncate
|
||||
:title="deployment.name"
|
||||
truncate-target="child"
|
||||
class="deploy-link label-truncate"
|
||||
>
|
||||
<a
|
||||
:href="deployment.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
class="js-deploy-meta"
|
||||
>
|
||||
{{ deployment.name }}
|
||||
</a>
|
||||
</tooltip-on-truncate>
|
||||
</template>
|
||||
<span
|
||||
v-if="hasDeploymentTime"
|
||||
v-gl-tooltip
|
||||
:title="deployment.deployed_at_formatted"
|
||||
class="js-deploy-time"
|
||||
>
|
||||
{{ deployTimeago }}
|
||||
</span>
|
||||
<memory-usage
|
||||
v-if="showMemoryUsage"
|
||||
:metrics-url="deployment.metrics_url"
|
||||
:metrics-monitoring-url="deployment.metrics_monitoring_url"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<template v-if="hasExternalUrls">
|
||||
<filtered-search-dropdown
|
||||
v-if="shouldRenderDropdown"
|
||||
class="js-mr-wigdet-deployment-dropdown inline"
|
||||
:items="deployment.changes"
|
||||
:main-action-link="deploymentExternalUrl"
|
||||
filter-key="path"
|
||||
>
|
||||
<template slot="mainAction" slot-scope="slotProps">
|
||||
<review-app-link
|
||||
:link="deploymentExternalUrl"
|
||||
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template slot="result" slot-scope="slotProps">
|
||||
<a
|
||||
:href="slotProps.result.external_url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
class="menu-item"
|
||||
>
|
||||
<strong class="str-truncated-100 append-bottom-0 d-block">
|
||||
{{ slotProps.result.path }}
|
||||
</strong>
|
||||
|
||||
<p class="text-secondary str-truncated-100 append-bottom-0 d-block">
|
||||
{{ slotProps.result.external_url }}
|
||||
</p>
|
||||
</a>
|
||||
</template>
|
||||
</filtered-search-dropdown>
|
||||
<template v-else>
|
||||
<review-app-link
|
||||
:link="deploymentExternalUrl"
|
||||
css-class="js-deploy-url deploy-link btn btn-default btn-sm inline"
|
||||
/>
|
||||
</template>
|
||||
<visual-review-app-link
|
||||
v-if="showVisualReviewApp"
|
||||
:link="deploymentExternalUrl"
|
||||
:app-metadata="visualReviewAppMeta"
|
||||
/>
|
||||
</template>
|
||||
<span
|
||||
v-if="deployment.stop_url"
|
||||
v-gl-tooltip
|
||||
:title="deployInProgressTooltip"
|
||||
class="d-inline-block"
|
||||
tabindex="0"
|
||||
>
|
||||
<loading-button
|
||||
:loading="isStopping"
|
||||
:disabled="isDeployInProgress"
|
||||
:title="__('Stop environment')"
|
||||
container-class="js-stop-env btn btn-default btn-sm inline prepend-left-4"
|
||||
@click="stopEnvironment"
|
||||
>
|
||||
<icon name="stop" />
|
||||
</loading-button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,8 @@
|
|||
// DEPLOYMENT STATUSES
|
||||
export const CREATED = 'created';
|
||||
export const MANUAL_DEPLOY = 'manual_deploy';
|
||||
export const WILL_DEPLOY = 'will_deploy';
|
||||
export const RUNNING = 'running';
|
||||
export const SUCCESS = 'success';
|
||||
export const FAILED = 'failed';
|
||||
export const CANCELED = 'canceled';
|
|
@ -0,0 +1,108 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import DeploymentInfo from './deployment_info.vue';
|
||||
import DeploymentViewButton from './deployment_view_button.vue';
|
||||
import DeploymentStopButton from './deployment_stop_button.vue';
|
||||
import { MANUAL_DEPLOY, WILL_DEPLOY, CREATED, RUNNING, SUCCESS } from './constants';
|
||||
|
||||
export default {
|
||||
// name: 'Deployment' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives
|
||||
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
|
||||
name: 'Deployment',
|
||||
components: {
|
||||
DeploymentInfo,
|
||||
DeploymentStopButton,
|
||||
DeploymentViewButton,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
deployment: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
showMetrics: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
showVisualReviewApp: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
visualReviewAppMeta: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({
|
||||
sourceProjectId: '',
|
||||
sourceProjectPath: '',
|
||||
mergeRequestId: '',
|
||||
appUrl: '',
|
||||
}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
canBeManuallyDeployed() {
|
||||
return this.computedDeploymentStatus === MANUAL_DEPLOY;
|
||||
},
|
||||
computedDeploymentStatus() {
|
||||
if (this.deployment.status === CREATED) {
|
||||
return this.isManual ? MANUAL_DEPLOY : WILL_DEPLOY;
|
||||
}
|
||||
return this.deployment.status;
|
||||
},
|
||||
hasExternalUrls() {
|
||||
return Boolean(this.deployment.external_url && this.deployment.external_url_formatted);
|
||||
},
|
||||
hasPreviousDeployment() {
|
||||
return Boolean(!this.isCurrent && this.deployment.deployed_at);
|
||||
},
|
||||
isCurrent() {
|
||||
return this.computedDeploymentStatus === SUCCESS;
|
||||
},
|
||||
isManual() {
|
||||
return Boolean(
|
||||
this.deployment.details &&
|
||||
this.deployment.details.playable_build &&
|
||||
this.deployment.details.playable_build.play_path,
|
||||
);
|
||||
},
|
||||
isDeployInProgress() {
|
||||
return this.deployment.status === RUNNING;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="deploy-heading">
|
||||
<div class="ci-widget media">
|
||||
<div class="media-body">
|
||||
<div class="deploy-body">
|
||||
<deployment-info
|
||||
:computed-deployment-status="computedDeploymentStatus"
|
||||
:deployment="deployment"
|
||||
:show-metrics="showMetrics"
|
||||
/>
|
||||
<div>
|
||||
<!-- show appropriate version of review app button -->
|
||||
<deployment-view-button
|
||||
v-if="hasExternalUrls"
|
||||
:is-current="isCurrent"
|
||||
:deployment="deployment"
|
||||
:show-visual-review-app="showVisualReviewApp"
|
||||
:visual-review-app-metadata="visualReviewAppMeta"
|
||||
/>
|
||||
<!-- if it is stoppable, show stop -->
|
||||
<deployment-stop-button
|
||||
v-if="deployment.stop_url"
|
||||
:is-deploy-in-progress="isDeployInProgress"
|
||||
:stop-url="deployment.stop_url"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,98 @@
|
|||
<script>
|
||||
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { __ } from '~/locale';
|
||||
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import MemoryUsage from './memory_usage.vue';
|
||||
import { MANUAL_DEPLOY, WILL_DEPLOY, RUNNING, SUCCESS, FAILED, CANCELED } from './constants';
|
||||
|
||||
export default {
|
||||
name: 'DeploymentInfo',
|
||||
components: {
|
||||
GlLink,
|
||||
MemoryUsage,
|
||||
TooltipOnTruncate,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
computedDeploymentStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
deployment: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
showMetrics: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
deployedTextMap: {
|
||||
[MANUAL_DEPLOY]: __('Can deploy manually to'),
|
||||
[WILL_DEPLOY]: __('Will deploy to'),
|
||||
[RUNNING]: __('Deploying to'),
|
||||
[SUCCESS]: __('Deployed to'),
|
||||
[FAILED]: __('Failed to deploy to'),
|
||||
[CANCELED]: __('Canceled deploy to'),
|
||||
},
|
||||
computed: {
|
||||
deployTimeago() {
|
||||
return this.timeFormated(this.deployment.deployed_at);
|
||||
},
|
||||
deployedText() {
|
||||
return this.$options.deployedTextMap[this.computedDeploymentStatus];
|
||||
},
|
||||
hasDeploymentTime() {
|
||||
return Boolean(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
|
||||
},
|
||||
hasDeploymentMeta() {
|
||||
return Boolean(this.deployment.url && this.deployment.name);
|
||||
},
|
||||
hasMetrics() {
|
||||
return Boolean(this.deployment.metrics_url);
|
||||
},
|
||||
showMemoryUsage() {
|
||||
return this.hasMetrics && this.showMetrics;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="js-deployment-info deployment-info">
|
||||
<template v-if="hasDeploymentMeta">
|
||||
<span>{{ deployedText }}</span>
|
||||
<tooltip-on-truncate
|
||||
:title="deployment.name"
|
||||
truncate-target="child"
|
||||
class="deploy-link label-truncate"
|
||||
>
|
||||
<gl-link
|
||||
:href="deployment.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
class="js-deploy-meta gl-font-size-12"
|
||||
>
|
||||
{{ deployment.name }}
|
||||
</gl-link>
|
||||
</tooltip-on-truncate>
|
||||
</template>
|
||||
<span
|
||||
v-if="hasDeploymentTime"
|
||||
v-gl-tooltip
|
||||
:title="deployment.deployed_at_formatted"
|
||||
class="js-deploy-time"
|
||||
>
|
||||
{{ deployTimeago }}
|
||||
</span>
|
||||
<memory-usage
|
||||
v-if="showMemoryUsage"
|
||||
:metrics-url="deployment.metrics_url"
|
||||
:metrics-monitoring-url="deployment.metrics_monitoring_url"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,83 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import createFlash from '~/flash';
|
||||
import MRWidgetService from '../../services/mr_widget_service';
|
||||
|
||||
export default {
|
||||
name: 'DeploymentStopButton',
|
||||
components: {
|
||||
LoadingButton,
|
||||
Icon,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
isDeployInProgress: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
stopUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isStopping: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
deployInProgressTooltip() {
|
||||
return this.isDeployInProgress
|
||||
? __('Stopping this environment is currently not possible as a deployment is in progress')
|
||||
: '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
stopEnvironment() {
|
||||
const msg = __('Are you sure you want to stop this environment?');
|
||||
const isConfirmed = confirm(msg); // eslint-disable-line
|
||||
|
||||
if (isConfirmed) {
|
||||
this.isStopping = true;
|
||||
|
||||
MRWidgetService.stopEnvironment(this.stopUrl)
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
if (data.redirect_url) {
|
||||
visitUrl(data.redirect_url);
|
||||
}
|
||||
|
||||
this.isStopping = false;
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash(
|
||||
__('Something went wrong while stopping this environment. Please try again.'),
|
||||
);
|
||||
this.isStopping = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-gl-tooltip :title="deployInProgressTooltip" class="d-inline-block" tabindex="0">
|
||||
<loading-button
|
||||
v-gl-tooltip
|
||||
:loading="isStopping"
|
||||
:disabled="isDeployInProgress"
|
||||
:title="__('Stop environment')"
|
||||
container-class="js-stop-env btn btn-default btn-sm inline prepend-left-4"
|
||||
@click="stopEnvironment"
|
||||
>
|
||||
<icon name="stop" />
|
||||
</loading-button>
|
||||
</span>
|
||||
</template>
|
|
@ -0,0 +1,99 @@
|
|||
<script>
|
||||
import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue';
|
||||
import ReviewAppLink from '../review_app_link.vue';
|
||||
|
||||
export default {
|
||||
name: 'DeploymentViewButton',
|
||||
components: {
|
||||
FilteredSearchDropdown,
|
||||
ReviewAppLink,
|
||||
VisualReviewAppLink: () =>
|
||||
import('ee_component/vue_merge_request_widget/components/visual_review_app_link.vue'),
|
||||
},
|
||||
props: {
|
||||
deployment: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
isCurrent: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
showVisualReviewApp: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
visualReviewAppMeta: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({
|
||||
sourceProjectId: '',
|
||||
sourceProjectPath: '',
|
||||
mergeRequestId: '',
|
||||
appUrl: '',
|
||||
}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
deploymentExternalUrl() {
|
||||
if (this.deployment.changes && this.deployment.changes.length === 1) {
|
||||
return this.deployment.changes[0].external_url;
|
||||
}
|
||||
return this.deployment.external_url;
|
||||
},
|
||||
shouldRenderDropdown() {
|
||||
return this.deployment.changes && this.deployment.changes.length > 1;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span>
|
||||
<filtered-search-dropdown
|
||||
v-if="shouldRenderDropdown"
|
||||
class="js-mr-wigdet-deployment-dropdown inline"
|
||||
:items="deployment.changes"
|
||||
:main-action-link="deploymentExternalUrl"
|
||||
filter-key="path"
|
||||
>
|
||||
<template slot="mainAction" slot-scope="slotProps">
|
||||
<review-app-link
|
||||
:is-current="isCurrent"
|
||||
:link="deploymentExternalUrl"
|
||||
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template slot="result" slot-scope="slotProps">
|
||||
<a
|
||||
:href="slotProps.result.external_url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
class="js-deploy-url-menu-item menu-item"
|
||||
>
|
||||
<strong class="str-truncated-100 append-bottom-0 d-block">
|
||||
{{ slotProps.result.path }}
|
||||
</strong>
|
||||
|
||||
<p class="text-secondary str-truncated-100 append-bottom-0 d-block">
|
||||
{{ slotProps.result.external_url }}
|
||||
</p>
|
||||
</a>
|
||||
</template>
|
||||
</filtered-search-dropdown>
|
||||
<template v-else>
|
||||
<review-app-link
|
||||
:is-current="isCurrent"
|
||||
:link="deploymentExternalUrl"
|
||||
css-class="js-deploy-url deploy-link btn btn-default btn-sm inline"
|
||||
/>
|
||||
</template>
|
||||
<visual-review-app-link
|
||||
v-if="showVisualReviewApp"
|
||||
:link="deploymentExternalUrl"
|
||||
:app-metadata="visualReviewAppMeta"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import statusCodes from '../../lib/utils/http_status';
|
||||
import { bytesToMiB } from '../../lib/utils/number_utils';
|
||||
import { backOff } from '../../lib/utils/common_utils';
|
||||
import MemoryGraph from '../../vue_shared/components/memory_graph.vue';
|
||||
import MRWidgetService from '../services/mr_widget_service';
|
||||
import statusCodes from '~/lib/utils/http_status';
|
||||
import { bytesToMiB } from '~/lib/utils/number_utils';
|
||||
import { backOff } from '~/lib/utils/common_utils';
|
||||
import MemoryGraph from '~/vue_shared/components/memory_graph.vue';
|
||||
import MRWidgetService from '../../services/mr_widget_service';
|
||||
|
||||
export default {
|
||||
name: 'MemoryUsage',
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import ArtifactsApp from './artifacts_list_app.vue';
|
||||
import Deployment from './deployment.vue';
|
||||
import Deployment from './deployment/deployment.vue';
|
||||
import MrWidgetContainer from './mr_widget_container.vue';
|
||||
import MrWidgetPipeline from './mr_widget_pipeline.vue';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { __ } from '~/locale';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
|
@ -6,13 +7,22 @@ export default {
|
|||
Icon,
|
||||
},
|
||||
props: {
|
||||
cssClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isCurrent: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
link: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
cssClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
linkText() {
|
||||
return this.isCurrent ? __('View app') : __('View previous app');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -26,6 +36,6 @@ export default {
|
|||
data-track-event="open_review_app"
|
||||
data-track-label="review_app"
|
||||
>
|
||||
{{ __('View app') }} <icon class="fgray" name="external-link" />
|
||||
{{ linkText }} <icon class="fgray" name="external-link" />
|
||||
</a>
|
||||
</template>
|
||||
|
|
|
@ -10,7 +10,7 @@ import createFlash from '../flash';
|
|||
import WidgetHeader from './components/mr_widget_header.vue';
|
||||
import WidgetMergeHelp from './components/mr_widget_merge_help.vue';
|
||||
import MrWidgetPipelineContainer from './components/mr_widget_pipeline_container.vue';
|
||||
import Deployment from './components/deployment.vue';
|
||||
import Deployment from './components/deployment/deployment.vue';
|
||||
import WidgetRelatedLinks from './components/mr_widget_related_links.vue';
|
||||
import MrWidgetAlertMessage from './components/mr_widget_alert_message.vue';
|
||||
import MergedState from './components/states/mr_widget_merged.vue';
|
||||
|
|
|
@ -10,6 +10,7 @@ class Projects::ReleasesController < Projects::ApplicationController
|
|||
push_frontend_feature_flag(:release_evidence_collection, project)
|
||||
end
|
||||
before_action :authorize_update_release!, only: %i[edit update]
|
||||
before_action :authorize_download_code!, only: [:evidence]
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
|
|
|
@ -662,9 +662,8 @@ module Ci
|
|||
def execute_hooks
|
||||
return unless project
|
||||
|
||||
build_data = Gitlab::DataBuilder::Build.build(self)
|
||||
project.execute_hooks(build_data.dup, :job_hooks)
|
||||
project.execute_services(build_data.dup, :job_hooks)
|
||||
project.execute_hooks(build_data.dup, :job_hooks) if project.has_active_hooks?(:job_hooks)
|
||||
project.execute_services(build_data.dup, :job_hooks) if project.has_active_services?(:job_hooks)
|
||||
end
|
||||
|
||||
def browsable_artifacts?
|
||||
|
@ -873,6 +872,10 @@ module Ci
|
|||
|
||||
private
|
||||
|
||||
def build_data
|
||||
@build_data ||= Gitlab::DataBuilder::Build.build(self)
|
||||
end
|
||||
|
||||
def successful_deployment_status
|
||||
if deployment&.last?
|
||||
:last
|
||||
|
|
|
@ -38,7 +38,7 @@ class CohortsService
|
|||
|
||||
{
|
||||
registration_month: registration_month,
|
||||
activity_months: activity_months,
|
||||
activity_months: activity_months[1..-1],
|
||||
total: activity_months.first[:total],
|
||||
inactive: inactive
|
||||
}
|
||||
|
|
|
@ -1,25 +1,32 @@
|
|||
- number_of_data_columns = @cohorts[:months_included] - 1
|
||||
.bs-callout.clearfix
|
||||
%p
|
||||
User cohorts are shown for the last #{@cohorts[:months_included]}
|
||||
months. Only users with activity are counted in the cohort total; inactive
|
||||
users are counted separately.
|
||||
= s_("Cohorts|User cohorts are shown for the last %{months_included} months. Only users with activity are counted in the 'New users' column; inactive users are counted separately.") % { months_included: @cohorts[:months_included] }
|
||||
= link_to icon('question-circle'), help_page_path('user/instance_statistics/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank'
|
||||
|
||||
.table-holder
|
||||
.table-holder.d-xl-table
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
%th Registration month
|
||||
%th Inactive users
|
||||
%th Cohort total
|
||||
- @cohorts[:months_included].times do |i|
|
||||
%th Month #{i}
|
||||
%th.border-right.pt-4{ colspan: 3 }
|
||||
%th.font-weight-bold.pt-4{ colspan: number_of_data_columns }
|
||||
= s_("Cohorts|Returning users")
|
||||
%tr
|
||||
%th.border-top-0
|
||||
= s_("Cohorts|Registration month")
|
||||
%th.border-top-0
|
||||
= s_("Cohorts|Inactive users")
|
||||
%th.border-top-0.border-right
|
||||
= s_("Cohorts|New users")
|
||||
- number_of_data_columns.times do |i|
|
||||
%th.border-top-0
|
||||
= s_("Cohorts|Month %{month_index}") % { month_index: i + 1 }
|
||||
%tbody
|
||||
- @cohorts[:cohorts].each do |cohort|
|
||||
%tr
|
||||
%td= cohort[:registration_month]
|
||||
%td= cohort[:inactive]
|
||||
%td= cohort[:total]
|
||||
%td.border-right= cohort[:total]
|
||||
- cohort[:activity_months].each do |activity_month|
|
||||
%td
|
||||
- next if cohort[:total] == '0'
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
#js-registry-settings{ data: { registry_settings_endpoint: '',
|
||||
help_page_path: help_page_path('user/project/operations/linking_to_an_external_dashboard') } }
|
|
@ -59,3 +59,14 @@
|
|||
.settings-content
|
||||
= render 'projects/triggers/index'
|
||||
|
||||
- if Feature.enabled?(:registry_retention_policies_settings, @project)
|
||||
%section.settings.no-animate#js-registry-polcies{ class: ('expanded' if expanded) }
|
||||
.settings-header
|
||||
%h4
|
||||
= _("Container Registry tag expiration policies")
|
||||
%button.btn.js-settings-toggle{ type: 'button' }
|
||||
= expanded ? _('Collapse') : _('Expand')
|
||||
%p
|
||||
= _("Expiration policies for the Container Registry are a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD.")
|
||||
.settings-content
|
||||
= render 'projects/registry/settings/index'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Clean up the cohorts table
|
||||
merge_request: 20779
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update information and button text for deployment footer
|
||||
merge_request: 18918
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Evidence - Added restriction for guest on Release page
|
||||
merge_request: 21102
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Reduce Gitaly calls in BuildHooksWorker
|
||||
merge_request: 20365
|
||||
author:
|
||||
type: performance
|
|
@ -7,6 +7,17 @@ require 'gitlab/current_settings'
|
|||
Gitlab.ee do
|
||||
require 'elasticsearch/model'
|
||||
|
||||
### Monkey patches
|
||||
|
||||
Elasticsearch::Model::Response::Records.prepend GemExtensions::Elasticsearch::Model::Response::Records
|
||||
Elasticsearch::Model::Adapter::Multiple::Records.prepend GemExtensions::Elasticsearch::Model::Adapter::Multiple::Records
|
||||
Elasticsearch::Model::Indexing::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Indexing::InstanceMethods
|
||||
Elasticsearch::Model::Adapter::ActiveRecord::Importing.prepend GemExtensions::Elasticsearch::Model::Adapter::ActiveRecord::Importing
|
||||
Elasticsearch::Model::Client::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Client
|
||||
Elasticsearch::Model::Client::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
|
||||
Elasticsearch::Model::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
|
||||
Elasticsearch::Model.singleton_class.prepend GemExtensions::Elasticsearch::Model::Client
|
||||
|
||||
### Modified from elasticsearch-model/lib/elasticsearch/model.rb
|
||||
|
||||
[
|
||||
|
@ -32,15 +43,4 @@ Gitlab.ee do
|
|||
target.respond_to?(:as_indexed_json) ? target.__send__(:as_indexed_json, options) : super
|
||||
end
|
||||
CODE
|
||||
|
||||
### Monkey patches
|
||||
|
||||
Elasticsearch::Model::Response::Records.prepend GemExtensions::Elasticsearch::Model::Response::Records
|
||||
Elasticsearch::Model::Adapter::Multiple::Records.prepend GemExtensions::Elasticsearch::Model::Adapter::Multiple::Records
|
||||
Elasticsearch::Model::Indexing::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Indexing::InstanceMethods
|
||||
Elasticsearch::Model::Adapter::ActiveRecord::Importing.prepend GemExtensions::Elasticsearch::Model::Adapter::ActiveRecord::Importing
|
||||
Elasticsearch::Model::Client::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Client
|
||||
Elasticsearch::Model::Client::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
|
||||
Elasticsearch::Model::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
|
||||
Elasticsearch::Model.singleton_class.prepend GemExtensions::Elasticsearch::Model::Client
|
||||
end
|
||||
|
|
|
@ -88,8 +88,8 @@ def instrument_classes(instrumentation)
|
|||
instrumentation.instrument_instance_methods(Gitlab::Highlight)
|
||||
|
||||
Gitlab.ee do
|
||||
instrumentation.instrument_methods(Elasticsearch::Git::Repository)
|
||||
instrumentation.instrument_instance_methods(Elasticsearch::Git::Repository)
|
||||
instrumentation.instrument_instance_methods(Elastic::Latest::GitInstanceProxy)
|
||||
instrumentation.instrument_instance_methods(Elastic::Latest::GitClassProxy)
|
||||
|
||||
instrumentation.instrument_instance_methods(Search::GlobalService)
|
||||
instrumentation.instrument_instance_methods(Search::ProjectService)
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateGitlabSubscriptionHistories < ActiveRecord::Migration[5.2]
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
create_table :gitlab_subscription_histories do |t|
|
||||
t.datetime_with_timezone :gitlab_subscription_created_at
|
||||
t.datetime_with_timezone :gitlab_subscription_updated_at
|
||||
t.date :start_date
|
||||
t.date :end_date
|
||||
t.date :trial_ends_on
|
||||
t.integer :namespace_id, null: true
|
||||
t.integer :hosted_plan_id, null: true
|
||||
t.integer :max_seats_used
|
||||
t.integer :seats
|
||||
t.boolean :trial
|
||||
t.integer :change_type, limit: 2
|
||||
t.bigint :gitlab_subscription_id, null: false
|
||||
t.datetime_with_timezone :created_at
|
||||
end
|
||||
add_index :gitlab_subscription_histories, :gitlab_subscription_id
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :gitlab_subscription_histories
|
||||
end
|
||||
end
|
17
db/schema.rb
17
db/schema.rb
|
@ -1828,6 +1828,23 @@ ActiveRecord::Schema.define(version: 2019_12_02_031812) do
|
|||
t.index ["upload_id"], name: "index_geo_upload_deleted_events_on_upload_id"
|
||||
end
|
||||
|
||||
create_table "gitlab_subscription_histories", force: :cascade do |t|
|
||||
t.datetime_with_timezone "gitlab_subscription_created_at"
|
||||
t.datetime_with_timezone "gitlab_subscription_updated_at"
|
||||
t.date "start_date"
|
||||
t.date "end_date"
|
||||
t.date "trial_ends_on"
|
||||
t.integer "namespace_id"
|
||||
t.integer "hosted_plan_id"
|
||||
t.integer "max_seats_used"
|
||||
t.integer "seats"
|
||||
t.boolean "trial"
|
||||
t.integer "change_type", limit: 2
|
||||
t.bigint "gitlab_subscription_id", null: false
|
||||
t.datetime_with_timezone "created_at"
|
||||
t.index ["gitlab_subscription_id"], name: "index_gitlab_subscription_histories_on_gitlab_subscription_id"
|
||||
end
|
||||
|
||||
create_table "gitlab_subscriptions", force: :cascade do |t|
|
||||
t.datetime_with_timezone "created_at", null: false
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
|
|
|
@ -270,3 +270,15 @@ database: gitlabhq_production
|
|||
--------------------------------------------------
|
||||
up migration_id migration_name
|
||||
```
|
||||
|
||||
## Import common metrics
|
||||
|
||||
Sometimes you may need to re-import the common metrics that power the Metrics dashboards.
|
||||
|
||||
This could be as a result of [updating existing metrics](../../development/prometheus_metrics.md#update-existing-metrics), or as a [troubleshooting measure](../../user/project/integrations/prometheus.md#troubleshooting).
|
||||
|
||||
To re-import the metrics you can run:
|
||||
|
||||
```sh
|
||||
sudo gitlab-rake metrics:setup_common_metrics
|
||||
```
|
||||
|
|
|
@ -75,6 +75,7 @@ cannot be used as job names**:
|
|||
- `after_script`
|
||||
- `variables`
|
||||
- `cache`
|
||||
- `include`
|
||||
|
||||
### Using reserved keywords
|
||||
|
||||
|
|
|
@ -249,7 +249,7 @@ scss_lint` in the GitLab directory. SCSS Lint will also run in GitLab CI to
|
|||
catch any warnings.
|
||||
|
||||
If the Rake task is throwing warnings you don't understand, SCSS Lint's
|
||||
documentation includes [a full list of their linters][scss-lint-documentation](https://github.com/sds/scss-lint/blob/master/lib/scss_lint/linter/README.md).
|
||||
documentation includes [a full list of their linters](https://github.com/sds/scss-lint/blob/master/lib/scss_lint/linter/README.md).
|
||||
|
||||
### Fixing issues
|
||||
|
||||
|
|
|
@ -22,7 +22,9 @@ The requirement for adding a new metric is to make each query to have an unique
|
|||
|
||||
### Update existing metrics
|
||||
|
||||
After you add or change existing _common_ metric you have to create a new database migration that will query and update all existing metrics.
|
||||
After you add or change an existing common metric, you must [re-run the import script](../administration/raketasks/maintenance.md#import-common-metrics) that will query and update all existing metrics.
|
||||
|
||||
Or, you can create a database migration:
|
||||
|
||||
NOTE: **Note:**
|
||||
If a query metric (which is identified by `id:`) is removed it will not be removed from database by default.
|
||||
|
|
|
@ -348,7 +348,7 @@ project):
|
|||
echo-js:
|
||||
handler: echo-js
|
||||
source: ./echo-js
|
||||
runtime: https://gitlab.com/gitlab-org/serverless/runtimes/nodejs
|
||||
runtime: gitlab/runtimes/nodejs
|
||||
description: "node.js runtime function"
|
||||
environment:
|
||||
MY_FUNCTION: echo-js
|
||||
|
@ -379,10 +379,27 @@ subsequent lines contain the function attributes.
|
|||
|-----------|-------------|
|
||||
| `handler` | The function's name. |
|
||||
| `source` | Directory with sources of a functions. |
|
||||
| `runtime` (optional)| The runtime to be used to execute the function. When the runtime is not specified, we assume that `Dockerfile` is present in the function directory specified by `source`. |
|
||||
| `runtime` (optional)| The runtime to be used to execute the function. This can be a runtime alias (see [Runtime aliases](#runtime-aliases)), or it can be a full URL to a custom runtime repository. When the runtime is not specified, we assume that `Dockerfile` is present in the function directory specified by `source`. |
|
||||
| `description` | A short description of the function. |
|
||||
| `environment` | Sets an environment variable for the specific function only. |
|
||||
|
||||
#### Runtime aliases
|
||||
|
||||
The optional `runtime` parameter can refer to one of the following runtime aliases (also see [Supported runtimes](#supported-runtimes)):
|
||||
|
||||
| Runtime alias | Maintained by |
|
||||
|-------------|---------------|
|
||||
| `gitlab/runtimes/go` | GitLab |
|
||||
| `gitlab/runtimes/nodejs` | GitLab |
|
||||
| `gitlab/runtimes/ruby` | GitLab |
|
||||
| `openfaas/classic/csharp` | OpenFaaS |
|
||||
| `openfaas/classic/go` | OpenFaaS |
|
||||
| `openfaas/classic/node` | OpenFaaS |
|
||||
| `openfaas/classic/php7` | OpenFaaS |
|
||||
| `openfaas/classic/python` | OpenFaaS |
|
||||
| `openfaas/classic/python3` | OpenFaaS |
|
||||
| `openfaas/classic/ruby` | OpenFaaS |
|
||||
|
||||
After the `gitlab-ci.yml` template has been added and the `serverless.yml` file
|
||||
has been created, pushing a commit to your project will result in a CI pipeline
|
||||
being executed which will deploy each function as a Knative service. Once the
|
||||
|
|
|
@ -574,6 +574,7 @@ If the "No data found" screen continues to appear, it could be due to:
|
|||
are not labeled correctly. To test this, connect to the Prometheus server and
|
||||
[run a query](prometheus_library/kubernetes.html#metrics-supported), replacing `$CI_ENVIRONMENT_SLUG`
|
||||
with the name of your environment.
|
||||
- You may need to re-add the GitLab predefined common metrics. This can be done by running the [import common metrics rake task](../../../administration/raketasks/maintenance.md#import-common-metrics).
|
||||
|
||||
[autodeploy]: ../../../topics/autodevops/index.md#auto-deploy
|
||||
[kubernetes]: https://kubernetes.io
|
||||
|
|
|
@ -1319,7 +1319,7 @@ module API
|
|||
expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? }
|
||||
expose :commit_path, expose_nil: false
|
||||
expose :tag_path, expose_nil: false
|
||||
expose :evidence_sha, expose_nil: false
|
||||
expose :evidence_sha, expose_nil: false, if: ->(_, _) { can_download_code? }
|
||||
expose :assets do
|
||||
expose :assets_count, as: :count do |release, _|
|
||||
assets_to_exclude = can_download_code? ? [] : [:sources]
|
||||
|
@ -1329,7 +1329,7 @@ module API
|
|||
expose :links, using: Entities::Releases::Link do |release, options|
|
||||
release.links.sorted
|
||||
end
|
||||
expose :evidence_file_path, expose_nil: false
|
||||
expose :evidence_file_path, expose_nil: false, if: ->(_, _) { can_download_code? }
|
||||
end
|
||||
expose :_links do
|
||||
expose :merge_requests_url, expose_nil: false
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
namespace :metrics do
|
||||
desc "GitLab | Setup common metrics"
|
||||
task setup_common_metrics: :gitlab_environment do
|
||||
::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
|
||||
end
|
||||
end
|
|
@ -2942,6 +2942,9 @@ msgstr ""
|
|||
msgid "Callback URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can deploy manually to"
|
||||
msgstr ""
|
||||
|
||||
msgid "Can override approvers and approvals required per merge request"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2969,6 +2972,9 @@ msgstr ""
|
|||
msgid "Cancel this job"
|
||||
msgstr ""
|
||||
|
||||
msgid "Canceled deploy to"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancelling Preview"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4334,6 +4340,24 @@ msgstr ""
|
|||
msgid "Cohorts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cohorts|Inactive users"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cohorts|Month %{month_index}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cohorts|New users"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cohorts|Registration month"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cohorts|Returning users"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cohorts|User cohorts are shown for the last %{months_included} months. Only users with activity are counted in the 'New users' column; inactive users are counted separately."
|
||||
msgstr ""
|
||||
|
||||
msgid "Collapse"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4615,6 +4639,9 @@ msgstr ""
|
|||
msgid "Container Registry"
|
||||
msgstr ""
|
||||
|
||||
msgid "Container Registry tag expiration policies"
|
||||
msgstr ""
|
||||
|
||||
msgid "Container Scanning"
|
||||
msgstr ""
|
||||
|
||||
|
@ -7092,6 +7119,9 @@ msgstr ""
|
|||
msgid "Expiration date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Expiration policies for the Container Registry are a perfect solution for keeping the Registry space down while still enjoying the full power of GitLab CI/CD."
|
||||
msgstr ""
|
||||
|
||||
msgid "Expired"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12141,6 +12171,9 @@ msgstr ""
|
|||
msgid "Package was removed"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Automatically remove extra images that aren't designed to be kept."
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Copy Maven XML"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12180,6 +12213,9 @@ msgstr ""
|
|||
msgid "PackageRegistry|Installation"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Keep and protect the images that matter most."
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab."
|
||||
msgstr ""
|
||||
|
||||
|
@ -12192,12 +12228,18 @@ msgstr ""
|
|||
msgid "PackageRegistry|Package installation"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Read more about the %{helpLinkStart}Container Registry tag retention policies%{helpLinkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Registry Setup"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Remove package"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Tag retention policies are designed to:"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|There are no packages yet"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19533,6 +19575,9 @@ msgstr ""
|
|||
msgid "View open merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "View previous app"
|
||||
msgstr ""
|
||||
|
||||
msgid "View project labels"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -154,7 +154,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
.and_return(merge_request)
|
||||
end
|
||||
|
||||
it 'does not serialize builds in exposed stages', :sidekiq_might_not_need_inline do
|
||||
it 'does not serialize builds in exposed stages' do
|
||||
get_show_json
|
||||
|
||||
json_response.dig('pipeline', 'details', 'stages').tap do |stages|
|
||||
|
@ -183,7 +183,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
context 'job is cancelable' do
|
||||
let(:job) { create(:ci_build, :running, pipeline: pipeline) }
|
||||
|
||||
it 'cancel_path is present with correct redirect', :sidekiq_might_not_need_inline do
|
||||
it 'cancel_path is present with correct redirect' do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('job/job_details')
|
||||
expect(json_response['cancel_path']).to include(CGI.escape(json_response['build_path']))
|
||||
|
@ -193,7 +193,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
context 'with web terminal' do
|
||||
let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) }
|
||||
|
||||
it 'exposes the terminal path', :sidekiq_might_not_need_inline do
|
||||
it 'exposes the terminal path' do
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('job/job_details')
|
||||
expect(json_response['terminal_path']).to match(%r{/terminal})
|
||||
|
@ -268,7 +268,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
project.add_maintainer(user) # Need to be a maintianer to view cluster.path
|
||||
end
|
||||
|
||||
it 'exposes the deployment information', :sidekiq_might_not_need_inline do
|
||||
it 'exposes the deployment information' do
|
||||
get_show_json
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -292,7 +292,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'user can edit runner', :sidekiq_might_not_need_inline do
|
||||
it 'user can edit runner' do
|
||||
get_show_json
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -312,7 +312,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'user can not edit runner', :sidekiq_might_not_need_inline do
|
||||
it 'user can not edit runner' do
|
||||
get_show_json
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -331,7 +331,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'user can not edit runner', :sidekiq_might_not_need_inline do
|
||||
it 'user can not edit runner' do
|
||||
get_show_json
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
@ -412,7 +412,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
context 'when job has trace' do
|
||||
let(:job) { create(:ci_build, :running, :trace_live, pipeline: pipeline) }
|
||||
|
||||
it "has_trace is true", :sidekiq_might_not_need_inline do
|
||||
it "has_trace is true" do
|
||||
get_show_json
|
||||
|
||||
expect(response).to match_response_schema('job/job_details')
|
||||
|
@ -458,7 +458,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
create(:ci_pipeline_variable, pipeline: pipeline, key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1')
|
||||
end
|
||||
|
||||
context 'user is a maintainer', :sidekiq_might_not_need_inline do
|
||||
context 'user is a maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
||||
|
@ -512,7 +512,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
|
|||
|
||||
def get_show_json
|
||||
expect { get_show(id: job.id, format: :json) }
|
||||
.not_to change { Gitlab::GitalyClient.get_request_count }
|
||||
.to change { Gitlab::GitalyClient.get_request_count }.by(1) # ListCommitsByOid
|
||||
end
|
||||
|
||||
def get_show(**extra_params)
|
||||
|
|
|
@ -93,7 +93,7 @@ describe Projects::PipelinesController do
|
|||
end
|
||||
|
||||
context 'when performing gitaly calls', :request_store do
|
||||
it 'limits the Gitaly requests', :sidekiq_might_not_need_inline do
|
||||
it 'limits the Gitaly requests' do
|
||||
# Isolate from test preparation (Repository#exists? is also cached in RequestStore)
|
||||
RequestStore.end!
|
||||
RequestStore.clear!
|
||||
|
@ -101,8 +101,9 @@ describe Projects::PipelinesController do
|
|||
|
||||
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
|
||||
|
||||
# ListCommitsByOid, RepositoryExists, HasLocalBranches
|
||||
expect { get_pipelines_index_json }
|
||||
.to change { Gitlab::GitalyClient.get_request_count }.by(2)
|
||||
.to change { Gitlab::GitalyClient.get_request_count }.by(3)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -184,19 +184,39 @@ describe Projects::ReleasesController do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'returns the correct evidence summary as a json' do
|
||||
subject
|
||||
|
||||
expect(json_response).to eq(release.evidence.summary)
|
||||
end
|
||||
|
||||
context 'when the release was created before evidence existed' do
|
||||
it 'returns an empty json' do
|
||||
release.evidence.destroy
|
||||
|
||||
context 'when the user is a developer' do
|
||||
it 'returns the correct evidence summary as a json' do
|
||||
subject
|
||||
|
||||
expect(json_response).to eq({})
|
||||
expect(json_response).to eq(release.evidence.summary)
|
||||
end
|
||||
|
||||
context 'when the release was created before evidence existed' do
|
||||
before do
|
||||
release.evidence.destroy
|
||||
end
|
||||
|
||||
it 'returns an empty json' do
|
||||
subject
|
||||
|
||||
expect(json_response).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is a guest for the project' do
|
||||
before do
|
||||
project.add_guest(user)
|
||||
end
|
||||
|
||||
context 'when the project is private' do
|
||||
let(:project) { private_project }
|
||||
|
||||
it_behaves_like 'not found'
|
||||
end
|
||||
|
||||
context 'when the project is public' do
|
||||
it_behaves_like 'successful request'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,6 +43,7 @@ describe 'Database schema' do
|
|||
geo_nodes: %w[oauth_application_id],
|
||||
geo_repository_deleted_events: %w[project_id],
|
||||
geo_upload_deleted_events: %w[upload_id model_id],
|
||||
gitlab_subscription_histories: %w[gitlab_subscription_id hosted_plan_id namespace_id],
|
||||
import_failures: %w[project_id],
|
||||
identities: %w[user_id],
|
||||
issues: %w[last_edited_by_id state_id],
|
||||
|
|
|
@ -96,7 +96,7 @@ describe 'Merge request > User sees deployment widget', :js do
|
|||
visit project_merge_request_path(project, merge_request)
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_content("Failed to deploy to #{environment.name}")
|
||||
expect(page).to have_content("Canceled deploy to #{environment.name}")
|
||||
expect(page).not_to have_css('.js-deploy-time')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,6 +45,7 @@ describe('Issuable component', () => {
|
|||
...props,
|
||||
},
|
||||
sync: false,
|
||||
attachToDocument: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ describe('Issuables list component', () => {
|
|||
},
|
||||
localVue,
|
||||
sync: false,
|
||||
attachToDocument: true,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Registry List renders 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
|
||||
Tag retention policies are designed to:
|
||||
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Keep and protect the images that matter most.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
|
||||
Automatically remove extra images that aren't designed to be kept.
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Read more about the
|
||||
<a
|
||||
href="foo"
|
||||
target="_blank"
|
||||
>
|
||||
Container Registry tag retention policies
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,40 @@
|
|||
import Vuex from 'vuex';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import component from '~/registry/settings/components/registry_settings_app.vue';
|
||||
import { createStore } from '~/registry/settings/stores/';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe('Registry List', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
|
||||
const helpPagePath = 'foo';
|
||||
const findHelpLink = () => wrapper.find({ ref: 'help-link' }).find('a');
|
||||
|
||||
const mountComponent = (options = {}) =>
|
||||
shallowMount(component, {
|
||||
sync: false,
|
||||
store,
|
||||
...options,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore();
|
||||
store.dispatch('setInitialState', { helpPagePath });
|
||||
wrapper = mountComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders an help link dependant on the helphPagePath', () => {
|
||||
expect(findHelpLink().attributes('href')).toBe(helpPagePath);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import * as actions from '~/registry/settings/stores/actions';
|
||||
import * as types from '~/registry/settings/stores/mutation_types';
|
||||
import testAction from 'helpers/vuex_action_helper';
|
||||
|
||||
jest.mock('~/flash.js');
|
||||
|
||||
describe('Actions Registry Store', () => {
|
||||
describe('setInitialState', () => {
|
||||
it('should set the initial state', done => {
|
||||
testAction(
|
||||
actions.setInitialState,
|
||||
'foo',
|
||||
{},
|
||||
[{ type: types.SET_INITIAL_STATE, payload: 'foo' }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
import mutations from '~/registry/settings/stores/mutations';
|
||||
import * as types from '~/registry/settings/stores/mutation_types';
|
||||
import createState from '~/registry/settings/stores/state';
|
||||
|
||||
describe('Mutations Registry Store', () => {
|
||||
let mockState;
|
||||
|
||||
beforeEach(() => {
|
||||
mockState = createState();
|
||||
});
|
||||
|
||||
describe('SET_INITIAL_STATE', () => {
|
||||
it('should set the initial state', () => {
|
||||
const payload = { helpPagePath: 'foo', registrySettingsEndpoint: 'bar' };
|
||||
const expectedState = { ...mockState, ...payload };
|
||||
mutations[types.SET_INITIAL_STATE](mockState, payload);
|
||||
|
||||
expect(mockState.endpoint).toEqual(expectedState.endpoint);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
|
||||
|
||||
const deploymentMockData = {
|
||||
id: 15,
|
||||
name: 'review/diplo',
|
||||
url: '/root/review-apps/environments/15',
|
||||
stop_url: '/root/review-apps/environments/15/stop',
|
||||
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
|
||||
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
|
||||
external_url: 'http://gitlab.com.',
|
||||
external_url_formatted: 'gitlab',
|
||||
deployed_at: '2017-03-22T22:44:42.258Z',
|
||||
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
|
||||
details: {},
|
||||
status: SUCCESS,
|
||||
changes: [
|
||||
{
|
||||
path: 'index.html',
|
||||
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
|
||||
},
|
||||
{
|
||||
path: 'imgs/gallery.html',
|
||||
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
|
||||
},
|
||||
{
|
||||
path: 'about/',
|
||||
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default deploymentMockData;
|
|
@ -0,0 +1,194 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import DeploymentComponent from '~/vue_merge_request_widget/components/deployment/deployment.vue';
|
||||
import DeploymentInfo from '~/vue_merge_request_widget/components/deployment/deployment_info.vue';
|
||||
import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
|
||||
import DeploymentStopButton from '~/vue_merge_request_widget/components/deployment/deployment_stop_button.vue';
|
||||
import {
|
||||
CREATED,
|
||||
RUNNING,
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
CANCELED,
|
||||
} from '~/vue_merge_request_widget/components/deployment/constants';
|
||||
import deploymentMockData from './deployment_mock_data';
|
||||
|
||||
const deployDetail = {
|
||||
playable_build: {
|
||||
retry_path: '/root/test-deployments/-/jobs/1131/retry',
|
||||
play_path: '/root/test-deployments/-/jobs/1131/play',
|
||||
},
|
||||
isManual: true,
|
||||
};
|
||||
|
||||
describe('Deployment component', () => {
|
||||
let wrapper;
|
||||
|
||||
const factory = (options = {}) => {
|
||||
// This destroys any wrappers created before a nested call to factory reassigns it
|
||||
if (wrapper && wrapper.destroy) {
|
||||
wrapper.destroy();
|
||||
}
|
||||
wrapper = mount(DeploymentComponent, {
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
deployment: deploymentMockData,
|
||||
showMetrics: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('always renders DeploymentInfo', () => {
|
||||
expect(wrapper.find(DeploymentInfo).exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('status message and buttons', () => {
|
||||
const noActions = [];
|
||||
const noDetails = { isManual: false };
|
||||
const deployGroup = [DeploymentViewButton, DeploymentStopButton];
|
||||
|
||||
describe.each`
|
||||
status | previous | deploymentDetails | text | actionButtons
|
||||
${CREATED} | ${true} | ${deployDetail} | ${'Can deploy manually to'} | ${deployGroup}
|
||||
${CREATED} | ${true} | ${noDetails} | ${'Will deploy to'} | ${deployGroup}
|
||||
${CREATED} | ${false} | ${deployDetail} | ${'Can deploy manually to'} | ${noActions}
|
||||
${CREATED} | ${false} | ${noDetails} | ${'Will deploy to'} | ${noActions}
|
||||
${RUNNING} | ${true} | ${deployDetail} | ${'Deploying to'} | ${deployGroup}
|
||||
${RUNNING} | ${true} | ${noDetails} | ${'Deploying to'} | ${deployGroup}
|
||||
${RUNNING} | ${false} | ${deployDetail} | ${'Deploying to'} | ${noActions}
|
||||
${RUNNING} | ${false} | ${noDetails} | ${'Deploying to'} | ${noActions}
|
||||
${SUCCESS} | ${true} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
|
||||
${SUCCESS} | ${true} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
|
||||
${SUCCESS} | ${false} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
|
||||
${SUCCESS} | ${false} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
|
||||
${FAILED} | ${true} | ${deployDetail} | ${'Failed to deploy to'} | ${deployGroup}
|
||||
${FAILED} | ${true} | ${noDetails} | ${'Failed to deploy to'} | ${deployGroup}
|
||||
${FAILED} | ${false} | ${deployDetail} | ${'Failed to deploy to'} | ${noActions}
|
||||
${FAILED} | ${false} | ${noDetails} | ${'Failed to deploy to'} | ${noActions}
|
||||
${CANCELED} | ${true} | ${deployDetail} | ${'Canceled deploy to'} | ${deployGroup}
|
||||
${CANCELED} | ${true} | ${noDetails} | ${'Canceled deploy to'} | ${deployGroup}
|
||||
${CANCELED} | ${false} | ${deployDetail} | ${'Canceled deploy to'} | ${noActions}
|
||||
${CANCELED} | ${false} | ${noDetails} | ${'Canceled deploy to'} | ${noActions}
|
||||
`(
|
||||
'$status + previous: $previous + manual: $deploymentDetails.isManual',
|
||||
({ status, previous, deploymentDetails, text, actionButtons }) => {
|
||||
beforeEach(() => {
|
||||
const previousOrSuccess = Boolean(previous || status === SUCCESS);
|
||||
const updatedDeploymentData = {
|
||||
status,
|
||||
deployed_at: previous ? deploymentMockData.deployed_at : null,
|
||||
deployed_at_formatted: previous ? deploymentMockData.deployed_at_formatted : null,
|
||||
external_url: previousOrSuccess ? deploymentMockData.external_url : null,
|
||||
external_url_formatted: previousOrSuccess
|
||||
? deploymentMockData.external_url_formatted
|
||||
: null,
|
||||
stop_url: previousOrSuccess ? deploymentMockData.stop_url : null,
|
||||
details: deploymentDetails,
|
||||
};
|
||||
|
||||
factory({
|
||||
propsData: {
|
||||
showMetrics: false,
|
||||
deployment: {
|
||||
...deploymentMockData,
|
||||
...updatedDeploymentData,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`renders the text: ${text}`, () => {
|
||||
expect(wrapper.find(DeploymentInfo).text()).toContain(text);
|
||||
});
|
||||
|
||||
if (actionButtons.length > 0) {
|
||||
describe('renders the expected button group', () => {
|
||||
actionButtons.forEach(button => {
|
||||
it(`renders ${button.name}`, () => {
|
||||
expect(wrapper.find(button).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (actionButtons.length === 0) {
|
||||
describe('does not render the button group', () => {
|
||||
[DeploymentViewButton, DeploymentStopButton].forEach(button => {
|
||||
it(`does not render ${button.name}`, () => {
|
||||
expect(wrapper.find(button).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (actionButtons.includes(DeploymentViewButton)) {
|
||||
it('renders the View button with expected text', () => {
|
||||
if (status === SUCCESS) {
|
||||
expect(wrapper.find(DeploymentViewButton).text()).toContain('View app');
|
||||
} else {
|
||||
expect(wrapper.find(DeploymentViewButton).text()).toContain('View previous app');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('hasExternalUrls', () => {
|
||||
describe('when deployment has both external_url_formatted and external_url', () => {
|
||||
it('should return true', () => {
|
||||
expect(wrapper.vm.hasExternalUrls).toEqual(true);
|
||||
});
|
||||
|
||||
it('should render the View Button', () => {
|
||||
expect(wrapper.find(DeploymentViewButton).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when deployment has no external_url_formatted', () => {
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
deployment: { ...deploymentMockData, external_url_formatted: null },
|
||||
showMetrics: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false', () => {
|
||||
expect(wrapper.vm.hasExternalUrls).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not render the View Button', () => {
|
||||
expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when deployment has no external_url', () => {
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
deployment: { ...deploymentMockData, external_url: null },
|
||||
showMetrics: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false', () => {
|
||||
expect(wrapper.vm.hasExternalUrls).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not render the View Button', () => {
|
||||
expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,118 @@
|
|||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
|
||||
import ReviewAppLink from '~/vue_merge_request_widget/components/review_app_link.vue';
|
||||
import deploymentMockData from './deployment_mock_data';
|
||||
|
||||
describe('Deployment View App button', () => {
|
||||
let wrapper;
|
||||
|
||||
const factory = (options = {}) => {
|
||||
const localVue = createLocalVue();
|
||||
|
||||
wrapper = mount(localVue.extend(DeploymentViewButton), {
|
||||
localVue,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
deployment: deploymentMockData,
|
||||
isCurrent: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('text', () => {
|
||||
describe('when app is current', () => {
|
||||
it('shows View app', () => {
|
||||
expect(wrapper.find(ReviewAppLink).text()).toContain('View app');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when app is not current', () => {
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
deployment: deploymentMockData,
|
||||
isCurrent: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('shows View Previous app', () => {
|
||||
expect(wrapper.find(ReviewAppLink).text()).toContain('View previous app');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without changes', () => {
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
deployment: { ...deploymentMockData, changes: null },
|
||||
isCurrent: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the link to the review app without dropdown', () => {
|
||||
expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a single change', () => {
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
deployment: { ...deploymentMockData, changes: [deploymentMockData.changes[0]] },
|
||||
isCurrent: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the link to the review app without dropdown', () => {
|
||||
expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders the link to the review app linked to to the first change', () => {
|
||||
const expectedUrl = deploymentMockData.changes[0].external_url;
|
||||
const deployUrl = wrapper.find('.js-deploy-url');
|
||||
|
||||
expect(deployUrl.attributes().href).not.toBeNull();
|
||||
expect(deployUrl.attributes().href).toEqual(expectedUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with multiple changes', () => {
|
||||
beforeEach(() => {
|
||||
factory({
|
||||
propsData: {
|
||||
deployment: deploymentMockData,
|
||||
isCurrent: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the link to the review app with dropdown', () => {
|
||||
expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders all the links to the review apps', () => {
|
||||
const allUrls = wrapper.findAll('.js-deploy-url-menu-item').wrappers;
|
||||
const expectedUrls = deploymentMockData.changes.map(change => change.external_url);
|
||||
|
||||
expectedUrls.forEach((expectedUrl, idx) => {
|
||||
const deployUrl = allUrls[idx];
|
||||
|
||||
expect(deployUrl.attributes().href).not.toBeNull();
|
||||
expect(deployUrl.attributes().href).toEqual(expectedUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,313 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
|
||||
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
|
||||
import { getTimeago } from '~/lib/utils/datetime_utility';
|
||||
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Deployment component', () => {
|
||||
const Component = Vue.extend(deploymentComponent);
|
||||
let deploymentMockData;
|
||||
|
||||
beforeEach(() => {
|
||||
deploymentMockData = {
|
||||
id: 15,
|
||||
name: 'review/diplo',
|
||||
url: '/root/review-apps/environments/15',
|
||||
stop_url: '/root/review-apps/environments/15/stop',
|
||||
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
|
||||
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
|
||||
external_url: 'http://gitlab.com.',
|
||||
external_url_formatted: 'gitlab',
|
||||
deployed_at: '2017-03-22T22:44:42.258Z',
|
||||
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
|
||||
changes: [
|
||||
{
|
||||
path: 'index.html',
|
||||
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
|
||||
},
|
||||
{
|
||||
path: 'imgs/gallery.html',
|
||||
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
|
||||
},
|
||||
{
|
||||
path: 'about/',
|
||||
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
let vm;
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: true });
|
||||
});
|
||||
|
||||
describe('deployTimeago', () => {
|
||||
it('return formatted date', () => {
|
||||
const readable = getTimeago().format(deploymentMockData.deployed_at);
|
||||
|
||||
expect(vm.deployTimeago).toEqual(readable);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasExternalUrls', () => {
|
||||
it('should return true', () => {
|
||||
expect(vm.hasExternalUrls).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false when deployment has no external_url_formatted', () => {
|
||||
vm.deployment.external_url_formatted = null;
|
||||
|
||||
expect(vm.hasExternalUrls).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false when deployment has no external_url', () => {
|
||||
vm.deployment.external_url = null;
|
||||
|
||||
expect(vm.hasExternalUrls).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasDeploymentTime', () => {
|
||||
it('should return true', () => {
|
||||
expect(vm.hasDeploymentTime).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false when deployment has no deployed_at', () => {
|
||||
vm.deployment.deployed_at = null;
|
||||
|
||||
expect(vm.hasDeploymentTime).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false when deployment has no deployed_at_formatted', () => {
|
||||
vm.deployment.deployed_at_formatted = null;
|
||||
|
||||
expect(vm.hasDeploymentTime).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasDeploymentMeta', () => {
|
||||
it('should return true', () => {
|
||||
expect(vm.hasDeploymentMeta).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false when deployment has no url', () => {
|
||||
vm.deployment.url = null;
|
||||
|
||||
expect(vm.hasDeploymentMeta).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false when deployment has no name', () => {
|
||||
vm.deployment.name = null;
|
||||
|
||||
expect(vm.hasDeploymentMeta).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stopEnvironment', () => {
|
||||
const url = '/foo/bar';
|
||||
const returnPromise = () =>
|
||||
new Promise(resolve => {
|
||||
resolve({
|
||||
data: {
|
||||
redirect_url: url,
|
||||
},
|
||||
});
|
||||
});
|
||||
const mockStopEnvironment = () => {
|
||||
vm.stopEnvironment(deploymentMockData);
|
||||
return vm;
|
||||
};
|
||||
|
||||
it('should show a confirm dialog and call service.stopEnvironment when confirmed', done => {
|
||||
spyOn(window, 'confirm').and.returnValue(true);
|
||||
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
|
||||
const visitUrl = spyOnDependency(deploymentComponent, 'visitUrl').and.returnValue(true);
|
||||
vm = mockStopEnvironment();
|
||||
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
|
||||
setTimeout(() => {
|
||||
expect(visitUrl).toHaveBeenCalledWith(url);
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
|
||||
it('should show a confirm dialog but should not work if the dialog is rejected', () => {
|
||||
spyOn(window, 'confirm').and.returnValue(false);
|
||||
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
|
||||
vm = mockStopEnvironment();
|
||||
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders deployment name', () => {
|
||||
expect(vm.$el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(
|
||||
deploymentMockData.url,
|
||||
);
|
||||
|
||||
expect(vm.$el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
|
||||
});
|
||||
|
||||
it('renders external URL', () => {
|
||||
expect(vm.$el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(
|
||||
deploymentMockData.external_url,
|
||||
);
|
||||
|
||||
expect(vm.$el.querySelector('.js-deploy-url').innerText).toContain('View app');
|
||||
});
|
||||
|
||||
it('renders stop button', () => {
|
||||
expect(vm.$el.querySelector('.btn')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders deployment time', () => {
|
||||
expect(vm.$el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
|
||||
});
|
||||
|
||||
it('renders metrics component', () => {
|
||||
expect(vm.$el.querySelector('.js-mr-memory-usage')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with showMetrics enabled', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: true });
|
||||
});
|
||||
|
||||
it('shows metrics', () => {
|
||||
expect(vm.$el).toContainElement('.js-mr-memory-usage');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with showMetrics disabled', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: false });
|
||||
});
|
||||
|
||||
it('hides metrics', () => {
|
||||
expect(vm.$el).not.toContainElement('.js-mr-memory-usage');
|
||||
});
|
||||
});
|
||||
|
||||
describe('without changes', () => {
|
||||
beforeEach(() => {
|
||||
delete deploymentMockData.changes;
|
||||
|
||||
vm = mountComponent(Component, { deployment: { ...deploymentMockData }, showMetrics: true });
|
||||
});
|
||||
|
||||
it('renders the link to the review app without dropdown', () => {
|
||||
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
|
||||
expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a single change', () => {
|
||||
beforeEach(() => {
|
||||
deploymentMockData.changes = deploymentMockData.changes.slice(0, 1);
|
||||
|
||||
vm = mountComponent(Component, {
|
||||
deployment: { ...deploymentMockData },
|
||||
showMetrics: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the link to the review app without dropdown', () => {
|
||||
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
|
||||
expect(vm.$el.querySelector('.js-deploy-url')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders the link to the review app linked to to the first change', () => {
|
||||
const expectedUrl = deploymentMockData.changes[0].external_url;
|
||||
const deployUrl = vm.$el.querySelector('.js-deploy-url');
|
||||
|
||||
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
|
||||
expect(deployUrl).not.toBeNull();
|
||||
expect(deployUrl.href).toEqual(expectedUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deployment status', () => {
|
||||
describe('running', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
deployment: Object.assign({}, deploymentMockData, { status: 'running' }),
|
||||
showMetrics: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders information about running deployment', () => {
|
||||
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deploying to');
|
||||
});
|
||||
|
||||
it('renders disabled stop button', () => {
|
||||
expect(vm.$el.querySelector('.js-stop-env').getAttribute('disabled')).toBe('disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
deployment: Object.assign({}, deploymentMockData, { status: 'success' }),
|
||||
showMetrics: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders information about finished deployment', () => {
|
||||
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Deployed to');
|
||||
});
|
||||
});
|
||||
|
||||
describe('failed', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
deployment: Object.assign({}, deploymentMockData, { status: 'failed' }),
|
||||
showMetrics: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders information about finished deployment', () => {
|
||||
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain(
|
||||
'Failed to deploy to',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('created', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
deployment: Object.assign({}, deploymentMockData, { status: 'created' }),
|
||||
showMetrics: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders information about created deployment', () => {
|
||||
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain('Will deploy to');
|
||||
});
|
||||
});
|
||||
|
||||
describe('canceled', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
deployment: Object.assign({}, deploymentMockData, { status: 'canceled' }),
|
||||
showMetrics: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders information about canceled deployment', () => {
|
||||
expect(vm.$el.querySelector('.js-deployment-info').textContent).toContain(
|
||||
'Failed to deploy to',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
import Vue from 'vue';
|
||||
import deploymentStopComponent from '~/vue_merge_request_widget/components/deployment/deployment_stop_button.vue';
|
||||
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
|
||||
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
|
||||
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Deployment component', () => {
|
||||
const Component = Vue.extend(deploymentStopComponent);
|
||||
let deploymentMockData;
|
||||
|
||||
beforeEach(() => {
|
||||
deploymentMockData = {
|
||||
id: 15,
|
||||
name: 'review/diplo',
|
||||
url: '/root/review-apps/environments/15',
|
||||
stop_url: '/root/review-apps/environments/15/stop',
|
||||
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
|
||||
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
|
||||
external_url: 'http://gitlab.com.',
|
||||
external_url_formatted: 'gitlab',
|
||||
deployed_at: '2017-03-22T22:44:42.258Z',
|
||||
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
|
||||
deployment_manual_actions: [],
|
||||
status: SUCCESS,
|
||||
changes: [
|
||||
{
|
||||
path: 'index.html',
|
||||
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
|
||||
},
|
||||
{
|
||||
path: 'imgs/gallery.html',
|
||||
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
|
||||
},
|
||||
{
|
||||
path: 'about/',
|
||||
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
let vm;
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
stopUrl: deploymentMockData.stop_url,
|
||||
isDeployInProgress: false,
|
||||
});
|
||||
});
|
||||
|
||||
describe('stopEnvironment', () => {
|
||||
const url = '/foo/bar';
|
||||
const returnPromise = () =>
|
||||
new Promise(resolve => {
|
||||
resolve({
|
||||
data: {
|
||||
redirect_url: url,
|
||||
},
|
||||
});
|
||||
});
|
||||
const mockStopEnvironment = () => {
|
||||
vm.stopEnvironment(deploymentMockData);
|
||||
return vm;
|
||||
};
|
||||
|
||||
it('should show a confirm dialog and call service.stopEnvironment when confirmed', done => {
|
||||
spyOn(window, 'confirm').and.returnValue(true);
|
||||
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
|
||||
const visitUrl = spyOnDependency(deploymentStopComponent, 'visitUrl').and.returnValue(true);
|
||||
vm = mockStopEnvironment();
|
||||
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
|
||||
setTimeout(() => {
|
||||
expect(visitUrl).toHaveBeenCalledWith(url);
|
||||
done();
|
||||
}, 333);
|
||||
});
|
||||
|
||||
it('should show a confirm dialog but should not work if the dialog is rejected', () => {
|
||||
spyOn(window, 'confirm').and.returnValue(false);
|
||||
spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
|
||||
vm = mockStopEnvironment();
|
||||
|
||||
expect(window.confirm).toHaveBeenCalled();
|
||||
expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import MemoryUsage from '~/vue_merge_request_widget/components/memory_usage.vue';
|
||||
import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
|
||||
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
|
||||
|
||||
const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
|
||||
|
|
|
@ -8,6 +8,7 @@ describe('review app link', () => {
|
|||
const props = {
|
||||
link: '/review',
|
||||
cssClass: 'js-link',
|
||||
isCurrent: true,
|
||||
};
|
||||
let vm;
|
||||
let el;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
|
||||
|
||||
export default {
|
||||
id: 132,
|
||||
iid: 22,
|
||||
|
@ -290,15 +292,20 @@ export const mockStore = {
|
|||
name: 'bogus',
|
||||
external_url: 'https://fake.com',
|
||||
external_url_formatted: 'https://fake.com',
|
||||
status: SUCCESS,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'bogus-docs',
|
||||
external_url: 'https://fake.com',
|
||||
external_url_formatted: 'https://fake.com',
|
||||
status: SUCCESS,
|
||||
},
|
||||
],
|
||||
postMergeDeployments: [{ id: 0, name: 'prod' }, { id: 1, name: 'prod-docs' }],
|
||||
postMergeDeployments: [
|
||||
{ id: 0, name: 'prod', status: SUCCESS },
|
||||
{ id: 1, name: 'prod-docs', status: SUCCESS },
|
||||
],
|
||||
troubleshootingDocsPath: 'troubleshooting-docs-path',
|
||||
ciStatus: 'ci-status',
|
||||
hasCI: true,
|
||||
|
|
|
@ -6,6 +6,7 @@ import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
|
|||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import mockData from './mock_data';
|
||||
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
|
||||
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
|
||||
|
||||
const returnPromise = data =>
|
||||
new Promise(resolve => {
|
||||
|
@ -277,7 +278,9 @@ describe('mrWidgetOptions', () => {
|
|||
|
||||
describe('fetchDeployments', () => {
|
||||
it('should fetch deployments', done => {
|
||||
spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
|
||||
spyOn(vm.service, 'fetchDeployments').and.returnValue(
|
||||
returnPromise([{ id: 1, status: SUCCESS }]),
|
||||
);
|
||||
|
||||
vm.fetchPreMergeDeployments();
|
||||
|
||||
|
@ -554,7 +557,7 @@ describe('mrWidgetOptions', () => {
|
|||
deployed_at: '2017-03-22T22:44:42.258Z',
|
||||
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
|
||||
changes,
|
||||
status: 'success',
|
||||
status: SUCCESS,
|
||||
};
|
||||
|
||||
beforeEach(done => {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe API::Entities::Release do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:release) { create(:release, :with_evidence, project: project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:entity) { described_class.new(release, current_user: user) }
|
||||
|
||||
subject { entity.as_json }
|
||||
|
||||
describe 'evidence' do
|
||||
context 'when the current user can download code' do
|
||||
it 'exposes the evidence sha and the json path' do
|
||||
allow(Ability).to receive(:allowed?).and_call_original
|
||||
allow(Ability).to receive(:allowed?)
|
||||
.with(user, :download_code, project).and_return(true)
|
||||
|
||||
expect(subject[:evidence_sha]).to eq(release.evidence_sha)
|
||||
expect(subject[:assets][:evidence_file_path]).to eq(
|
||||
Gitlab::Routing.url_helpers.evidence_project_release_url(project,
|
||||
release.tag,
|
||||
format: :json)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the current user cannot download code' do
|
||||
it 'does not expose any evidence data' do
|
||||
allow(Ability).to receive(:allowed?).and_call_original
|
||||
allow(Ability).to receive(:allowed?)
|
||||
.with(user, :download_code, project).and_return(false)
|
||||
|
||||
expect(subject.keys).not_to include(:evidence_sha)
|
||||
expect(subject[:assets].keys).not_to include(:evidence_file_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4063,4 +4063,54 @@ describe Ci::Build do
|
|||
expect(job.invalid_dependencies).to eq([pre_stage_job_invalid])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute_hooks' do
|
||||
context 'with project hooks' do
|
||||
before do
|
||||
create(:project_hook, project: project, job_events: true)
|
||||
end
|
||||
|
||||
it 'execute hooks' do
|
||||
expect_any_instance_of(ProjectHook).to receive(:async_execute)
|
||||
|
||||
build.execute_hooks
|
||||
end
|
||||
end
|
||||
|
||||
context 'without relevant project hooks' do
|
||||
before do
|
||||
create(:project_hook, project: project, job_events: false)
|
||||
end
|
||||
|
||||
it 'does not execute a hook' do
|
||||
expect_any_instance_of(ProjectHook).not_to receive(:async_execute)
|
||||
|
||||
build.execute_hooks
|
||||
end
|
||||
end
|
||||
|
||||
context 'with project services' do
|
||||
before do
|
||||
create(:service, active: true, job_events: true, project: project)
|
||||
end
|
||||
|
||||
it 'execute services' do
|
||||
expect_any_instance_of(Service).to receive(:async_execute)
|
||||
|
||||
build.execute_hooks
|
||||
end
|
||||
end
|
||||
|
||||
context 'without relevant project services' do
|
||||
before do
|
||||
create(:service, active: true, job_events: false, project: project)
|
||||
end
|
||||
|
||||
it 'execute services' do
|
||||
expect_any_instance_of(Service).not_to receive(:async_execute)
|
||||
|
||||
build.execute_hooks
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,73 +22,73 @@ describe CohortsService do
|
|||
expected_cohorts = [
|
||||
{
|
||||
registration_month: month_start(11),
|
||||
activity_months: Array.new(12) { { total: 0, percentage: 0 } },
|
||||
activity_months: Array.new(11) { { total: 0, percentage: 0 } },
|
||||
total: 0,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(10),
|
||||
activity_months: [{ total: 2, percentage: 100 }] + Array.new(10) { { total: 1, percentage: 50 } },
|
||||
activity_months: Array.new(10) { { total: 1, percentage: 50 } },
|
||||
total: 2,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(9),
|
||||
activity_months: Array.new(10) { { total: 0, percentage: 0 } },
|
||||
activity_months: Array.new(9) { { total: 0, percentage: 0 } },
|
||||
total: 0,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(8),
|
||||
activity_months: [{ total: 2, percentage: 100 }] + Array.new(8) { { total: 1, percentage: 50 } },
|
||||
activity_months: Array.new(8) { { total: 1, percentage: 50 } },
|
||||
total: 2,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(7),
|
||||
activity_months: Array.new(8) { { total: 0, percentage: 0 } },
|
||||
activity_months: Array.new(7) { { total: 0, percentage: 0 } },
|
||||
total: 0,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(6),
|
||||
activity_months: [{ total: 2, percentage: 100 }] + Array.new(6) { { total: 1, percentage: 50 } },
|
||||
activity_months: Array.new(6) { { total: 1, percentage: 50 } },
|
||||
total: 2,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(5),
|
||||
activity_months: Array.new(6) { { total: 0, percentage: 0 } },
|
||||
activity_months: Array.new(5) { { total: 0, percentage: 0 } },
|
||||
total: 0,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(4),
|
||||
activity_months: [{ total: 2, percentage: 100 }] + Array.new(4) { { total: 1, percentage: 50 } },
|
||||
activity_months: Array.new(4) { { total: 1, percentage: 50 } },
|
||||
total: 2,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(3),
|
||||
activity_months: Array.new(4) { { total: 0, percentage: 0 } },
|
||||
activity_months: Array.new(3) { { total: 0, percentage: 0 } },
|
||||
total: 0,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(2),
|
||||
activity_months: [{ total: 2, percentage: 100 }] + Array.new(2) { { total: 1, percentage: 50 } },
|
||||
activity_months: Array.new(2) { { total: 1, percentage: 50 } },
|
||||
total: 2,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(1),
|
||||
activity_months: Array.new(2) { { total: 0, percentage: 0 } },
|
||||
activity_months: Array.new(1) { { total: 0, percentage: 0 } },
|
||||
total: 0,
|
||||
inactive: 0
|
||||
},
|
||||
{
|
||||
registration_month: month_start(0),
|
||||
activity_months: [{ total: 2, percentage: 100 }],
|
||||
activity_months: [],
|
||||
total: 2,
|
||||
inactive: 1
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue