Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a8f5aaa708
commit
bd7e8cd64b
|
|
@ -25,10 +25,9 @@ build-qa-image:
|
|||
- .build-images:rules:build-qa-image
|
||||
stage: build-images
|
||||
needs: []
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}"
|
||||
script:
|
||||
- !reference [.base-image-build, script]
|
||||
- echo $QA_IMAGE
|
||||
- /kaniko/executor --context=${CI_PROJECT_DIR} --dockerfile=${CI_PROJECT_DIR}/qa/Dockerfile --destination=${QA_IMAGE} --cache=true
|
||||
|
||||
# This image is used by:
|
||||
|
|
|
|||
|
|
@ -51,9 +51,10 @@ update-qa-cache:
|
|||
image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
|
||||
stage: qa
|
||||
retry: 0
|
||||
script:
|
||||
before_script:
|
||||
- source scripts/utils.sh
|
||||
- install_gitlab_gem
|
||||
script:
|
||||
- ./scripts/trigger-build omnibus
|
||||
|
||||
package-and-qa:
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ review-stop:
|
|||
extends:
|
||||
- .use-docker-in-docker
|
||||
image:
|
||||
name: ${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_REF_SLUG}
|
||||
name: ${QA_IMAGE}
|
||||
entrypoint: [""]
|
||||
stage: qa
|
||||
needs: ["build-qa-image", "review-deploy"]
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@
|
|||
- "{,ee/,jh/}spec/support/helpers/database/**/*"
|
||||
- "config/prometheus/common_metrics.yml" # Used by Gitlab::DatabaseImporters::CommonMetrics::Importer
|
||||
- "{,ee/,jh/}app/models/project_statistics.rb" # Used to calculate sizes in migration specs
|
||||
- "GITALY_SERVER_VERSION" # Has interactions with background migrations:https://gitlab.com/gitlab-org/gitlab/-/issues/336538
|
||||
# CI changes
|
||||
- ".gitlab-ci.yml"
|
||||
- ".gitlab/ci/**/*"
|
||||
|
|
@ -363,11 +364,19 @@
|
|||
when: never
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *ci-build-images-patterns
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *code-qa-patterns
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
- <<: *if-dot-com-gitlab-org-default-branch
|
||||
changes: *code-qa-patterns
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_SHA}"
|
||||
- <<: *if-dot-com-gitlab-org-schedule
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_SHA}"
|
||||
|
||||
.build-images:rules:build-assets-image:
|
||||
rules:
|
||||
|
|
@ -579,16 +588,24 @@
|
|||
when: never
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *ci-qa-patterns
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *qa-patterns
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-and-security-merge-request
|
||||
changes: *code-patterns
|
||||
when: manual
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-schedule
|
||||
allow_failure: true
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_COMMIT_SHA}"
|
||||
|
||||
###############
|
||||
# Rails rules #
|
||||
|
|
@ -1201,11 +1218,17 @@
|
|||
when: never
|
||||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *ci-review-patterns
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *frontend-patterns
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *code-qa-patterns
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
allow_failure: true
|
||||
|
||||
# The rule needs to be duplicated between `on_success` and `on_failure`
|
||||
|
|
@ -1241,9 +1264,13 @@
|
|||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *code-patterns
|
||||
when: manual
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
allow_failure: true
|
||||
- <<: *if-dot-com-gitlab-org-merge-request
|
||||
changes: *qa-patterns
|
||||
variables:
|
||||
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
|
||||
allow_failure: true
|
||||
|
||||
# The rule needs to be duplicated between `on_success` and `on_failure`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
<script>
|
||||
import { GlButton, GlModalDirective, GlTooltipDirective as GlTooltip, GlSprintf } from '@gitlab/ui';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
import { __, s__ } from '~/locale';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import DeleteEnvironmentModal from './delete_environment_modal.vue';
|
||||
import StopEnvironmentModal from './stop_environment_modal.vue';
|
||||
|
||||
export default {
|
||||
name: 'EnvironmentsDetailHeader',
|
||||
csrf,
|
||||
components: {
|
||||
GlButton,
|
||||
GlSprintf,
|
||||
TimeAgo,
|
||||
DeleteEnvironmentModal,
|
||||
StopEnvironmentModal,
|
||||
},
|
||||
directives: {
|
||||
GlModalDirective,
|
||||
GlTooltip,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
environment: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
canReadEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canAdminEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canUpdateEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canDestroyEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canStopEnvironment: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
cancelAutoStopPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
metricsPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
terminalPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
autoStopAtText: s__('Environments|Auto stops %{autoStopAt}'),
|
||||
metricsButtonTitle: __('See metrics'),
|
||||
metricsButtonText: __('Monitoring'),
|
||||
editButtonText: __('Edit'),
|
||||
stopButtonText: s__('Environments|Stop'),
|
||||
deleteButtonText: s__('Environments|Delete'),
|
||||
externalButtonTitle: s__('Environments|Open live environment'),
|
||||
externalButtonText: __('View deployment'),
|
||||
cancelAutoStopButtonTitle: __('Prevent environment from auto-stopping'),
|
||||
},
|
||||
computed: {
|
||||
shouldShowCancelAutoStopButton() {
|
||||
return this.environment.isAvailable && Boolean(this.environment.autoStopAt);
|
||||
},
|
||||
shouldShowExternalUrlButton() {
|
||||
return this.canReadEnvironment && Boolean(this.environment.externalUrl);
|
||||
},
|
||||
shouldShowStopButton() {
|
||||
return this.canStopEnvironment && this.environment.isAvailable;
|
||||
},
|
||||
shouldShowTerminalButton() {
|
||||
return this.canAdminEnvironment && this.environment.hasTerminals;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<header class="top-area gl-justify-content-between">
|
||||
<div class="gl-display-flex gl-flex-grow-1 gl-align-items-center">
|
||||
<h3 class="page-title">
|
||||
{{ environment.name }}
|
||||
</h3>
|
||||
<p v-if="shouldShowCancelAutoStopButton" class="gl-mb-0 gl-ml-3" data-testid="auto-stops-at">
|
||||
<gl-sprintf :message="$options.i18n.autoStopAtText">
|
||||
<template #autoStopAt>
|
||||
<time-ago :time="environment.autoStopAt" />
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
</div>
|
||||
<div class="nav-controls gl-my-1">
|
||||
<form method="POST" :action="cancelAutoStopPath" data-testid="cancel-auto-stop-form">
|
||||
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
|
||||
<gl-button
|
||||
v-if="shouldShowCancelAutoStopButton"
|
||||
v-gl-tooltip.hover
|
||||
data-testid="cancel-auto-stop-button"
|
||||
:title="$options.i18n.cancelAutoStopButtonTitle"
|
||||
type="submit"
|
||||
icon="thumbtack"
|
||||
/>
|
||||
</form>
|
||||
<gl-button
|
||||
v-if="shouldShowTerminalButton"
|
||||
data-testid="terminal-button"
|
||||
:href="terminalPath"
|
||||
icon="terminal"
|
||||
/>
|
||||
<gl-button
|
||||
v-if="shouldShowExternalUrlButton"
|
||||
v-gl-tooltip.hover
|
||||
data-testid="external-url-button"
|
||||
:title="$options.i18n.externalButtonTitle"
|
||||
:href="environment.externalUrl"
|
||||
icon="external-link"
|
||||
target="_blank"
|
||||
>{{ $options.i18n.externalButtonText }}</gl-button
|
||||
>
|
||||
<gl-button
|
||||
v-if="canReadEnvironment"
|
||||
data-testid="metrics-button"
|
||||
:href="metricsPath"
|
||||
:title="$options.i18n.metricsButtonTitle"
|
||||
icon="chart"
|
||||
class="gl-mr-2"
|
||||
>
|
||||
{{ $options.i18n.metricsButtonText }}
|
||||
</gl-button>
|
||||
<gl-button v-if="canUpdateEnvironment" data-testid="edit-button" :href="updatePath">
|
||||
{{ $options.i18n.editButtonText }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
v-if="shouldShowStopButton"
|
||||
v-gl-modal-directive="'stop-environment-modal'"
|
||||
data-testid="stop-button"
|
||||
icon="stop"
|
||||
variant="danger"
|
||||
>
|
||||
{{ $options.i18n.stopButtonText }}
|
||||
</gl-button>
|
||||
<gl-button
|
||||
v-if="canDestroyEnvironment"
|
||||
v-gl-modal-directive="'delete-environment-modal'"
|
||||
data-testid="destroy-button"
|
||||
variant="danger"
|
||||
>
|
||||
{{ $options.i18n.deleteButtonText }}
|
||||
</gl-button>
|
||||
</div>
|
||||
<delete-environment-modal v-if="canDestroyEnvironment" :environment="environment" />
|
||||
<stop-environment-modal v-if="shouldShowStopButton" :environment="environment" />
|
||||
</header>
|
||||
</template>
|
||||
|
|
@ -108,7 +108,19 @@ export default {
|
|||
|
||||
this.service
|
||||
.postAction(endpoint)
|
||||
.then(() => this.fetchEnvironments())
|
||||
.then(() => {
|
||||
// Originally, the detail page buttons were implemented as <form>s that POSTed
|
||||
// to the server, which would naturally result in a page refresh.
|
||||
// When environment details page was converted to Vue, the buttons were updated to trigger
|
||||
// HTTP requests using `axios`, which did not cause a refresh on completion.
|
||||
// To preserve the original behavior, we manually reload the page when
|
||||
// network requests complete successfully.
|
||||
if (!this.isDetailView) {
|
||||
this.fetchEnvironments();
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.isLoading = false;
|
||||
createFlash({
|
||||
|
|
|
|||
|
|
@ -1,30 +1,48 @@
|
|||
import Vue from 'vue';
|
||||
import DeleteEnvironmentModal from './components/delete_environment_modal.vue';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import EnvironmentsDetailHeader from './components/environments_detail_header.vue';
|
||||
import environmentsMixin from './mixins/environments_mixin';
|
||||
|
||||
export default () => {
|
||||
const el = document.getElementById('delete-environment-modal');
|
||||
export const initHeader = () => {
|
||||
const el = document.getElementById('environments-detail-view-header');
|
||||
const container = document.getElementById('environments-detail-view');
|
||||
const dataset = convertObjectPropsToCamelCase(JSON.parse(container.dataset.details));
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
components: {
|
||||
DeleteEnvironmentModal,
|
||||
},
|
||||
mixins: [environmentsMixin],
|
||||
data() {
|
||||
const environment = JSON.parse(JSON.stringify(container.dataset));
|
||||
environment.delete_path = environment.deletePath;
|
||||
environment.onSingleEnvironmentPage = true;
|
||||
const environment = {
|
||||
name: dataset.name,
|
||||
id: Number(dataset.id),
|
||||
externalUrl: dataset.externalUrl,
|
||||
isAvailable: dataset.isEnvironmentAvailable,
|
||||
hasTerminals: dataset.hasTerminals,
|
||||
autoStopAt: dataset.autoStopAt,
|
||||
onSingleEnvironmentPage: true,
|
||||
// TODO: These two props are snake_case because the environments_mixin file uses
|
||||
// them and the mixin is imported in several files. It would be nice to conver them to camelCase.
|
||||
stop_path: dataset.environmentStopPath,
|
||||
delete_path: dataset.environmentDeletePath,
|
||||
};
|
||||
|
||||
return {
|
||||
environment,
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('delete-environment-modal', {
|
||||
return createElement(EnvironmentsDetailHeader, {
|
||||
props: {
|
||||
environment: this.environment,
|
||||
canDestroyEnvironment: dataset.canDestroyEnvironment,
|
||||
canUpdateEnvironment: dataset.canUpdateEnvironment,
|
||||
canReadEnvironment: dataset.canReadEnvironment,
|
||||
canStopEnvironment: dataset.canStopEnvironment,
|
||||
canAdminEnvironment: dataset.canAdminEnvironment,
|
||||
cancelAutoStopPath: dataset.environmentCancelAutoStopPath,
|
||||
terminalPath: dataset.environmentTerminalPath,
|
||||
metricsPath: dataset.environmentMetricsPath,
|
||||
updatePath: dataset.environmentEditPath,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners
|
|||
import { FILTERED_SEARCH } from '~/pages/constants';
|
||||
import initFilteredSearch from '~/pages/search/init_filtered_search';
|
||||
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
|
||||
import { initRunnerList } from '~/runner/runner_list';
|
||||
import { initAdminRunners } from '~/runner/admin_runners';
|
||||
|
||||
initFilteredSearch({
|
||||
page: FILTERED_SEARCH.ADMIN_RUNNERS,
|
||||
|
|
@ -13,5 +13,5 @@ initFilteredSearch({
|
|||
initInstallRunner();
|
||||
|
||||
if (gon.features?.runnerListViewVueUi) {
|
||||
initRunnerList();
|
||||
initAdminRunners();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import initShowEnvironment from '~/environments/mount_show';
|
||||
import { initHeader } from '~/environments/mount_show';
|
||||
|
||||
initShowEnvironment();
|
||||
initHeader();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import {
|
||||
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
|
||||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
CLEANUP_STATUS_SCHEDULED,
|
||||
CLEANUP_STATUS_ONGOING,
|
||||
CLEANUP_STATUS_UNFINISHED,
|
||||
|
|
@ -34,7 +34,7 @@ export default {
|
|||
CLEANUP_STATUS_SCHEDULED,
|
||||
CLEANUP_STATUS_ONGOING,
|
||||
CLEANUP_STATUS_UNFINISHED,
|
||||
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
|
||||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
},
|
||||
computed: {
|
||||
showStatus() {
|
||||
|
|
@ -61,7 +61,7 @@ export default {
|
|||
</span>
|
||||
<gl-icon
|
||||
v-if="failedDelete"
|
||||
v-gl-tooltip="{ title: $options.i18n.ASYNC_DELETE_IMAGE_ERROR_MESSAGE }"
|
||||
v-gl-tooltip="{ title: $options.i18n.CLEANUP_TIMED_OUT_ERROR_MESSAGE }"
|
||||
:size="14"
|
||||
class="gl-text-black-normal"
|
||||
data-testid="extra-info"
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ import RunnerPagination from '../components/runner_pagination.vue';
|
|||
import RunnerTypeHelp from '../components/runner_type_help.vue';
|
||||
import { INSTANCE_TYPE, I18N_FETCH_ERROR } from '../constants';
|
||||
import getRunnersQuery from '../graphql/get_runners.query.graphql';
|
||||
import { captureException } from '../sentry_utils';
|
||||
import {
|
||||
fromUrlQueryToSearch,
|
||||
fromSearchToUrl,
|
||||
fromSearchToVariables,
|
||||
} from './runner_search_utils';
|
||||
} from '../runner_search_utils';
|
||||
import { captureException } from '../sentry_utils';
|
||||
|
||||
export default {
|
||||
name: 'RunnerListApp',
|
||||
name: 'AdminRunnersApp',
|
||||
components: {
|
||||
RunnerFilteredSearchBar,
|
||||
RunnerList,
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import RunnerDetailsApp from './runner_list_app.vue';
|
||||
import AdminRunnersApp from './admin_runners_app.vue';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
export const initRunnerList = (selector = '#js-runner-list') => {
|
||||
export const initAdminRunners = (selector = '#js-admin-runners') => {
|
||||
const el = document.querySelector(selector);
|
||||
|
||||
if (!el) {
|
||||
|
|
@ -32,7 +32,7 @@ export const initRunnerList = (selector = '#js-runner-list') => {
|
|||
runnerInstallHelpPage,
|
||||
},
|
||||
render(h) {
|
||||
return h(RunnerDetailsApp, {
|
||||
return h(AdminRunnersApp, {
|
||||
props: {
|
||||
activeRunnersCount: parseInt(activeRunnersCount, 10),
|
||||
registrationToken,
|
||||
|
|
@ -16,7 +16,7 @@ import {
|
|||
PARAM_KEY_BEFORE,
|
||||
DEFAULT_SORT,
|
||||
RUNNER_PAGE_SIZE,
|
||||
} from '../constants';
|
||||
} from './constants';
|
||||
|
||||
const getPaginationFromParams = (params) => {
|
||||
const page = parseInt(params[PARAM_KEY_PAGE], 10);
|
||||
|
|
@ -509,6 +509,24 @@ span.idiff {
|
|||
}
|
||||
}
|
||||
|
||||
.version-link {
|
||||
@include gl-display-inline-block;
|
||||
@include gl-align-self-center;
|
||||
@include gl-mt-2;
|
||||
@include gl-w-5;
|
||||
@include gl-h-5;
|
||||
@include gl-float-left;
|
||||
background-color: $gray-400;
|
||||
mask-image: asset_url('icons-stacked.svg#doc-versions');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: cover;
|
||||
mask-position: center;
|
||||
|
||||
&:hover {
|
||||
background-color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// IMPORTANT PERFORMANCE OPTIMIZATION BELOW
|
||||
//
|
||||
|
|
|
|||
|
|
@ -65,4 +65,31 @@ module EnvironmentHelper
|
|||
content_tag(:span, text, class: klass)
|
||||
end
|
||||
end
|
||||
|
||||
def environments_detail_data(user, project, environment)
|
||||
{
|
||||
name: environment.name,
|
||||
id: environment.id,
|
||||
external_url: environment.external_url,
|
||||
can_update_environment: can?(current_user, :update_environment, environment),
|
||||
can_destroy_environment: can_destroy_environment?(environment),
|
||||
can_read_environment: can?(current_user, :read_environment, environment),
|
||||
can_stop_environment: can?(current_user, :stop_environment, environment),
|
||||
can_admin_environment: can?(current_user, :admin_environment, project),
|
||||
environment_metrics_path: environment_metrics_path(environment),
|
||||
environments_fetch_path: project_environments_path(project, format: :json),
|
||||
environment_edit_path: edit_project_environment_path(project, environment),
|
||||
environment_stop_path: stop_project_environment_path(project, environment),
|
||||
environment_delete_path: environment_delete_path(environment),
|
||||
environment_cancel_auto_stop_path: cancel_auto_stop_project_environment_path(project, environment),
|
||||
environment_terminal_path: terminal_project_environment_path(project, environment),
|
||||
has_terminals: environment.has_terminals?,
|
||||
is_environment_available: environment.available?,
|
||||
auto_stop_at: environment.auto_stop_at
|
||||
}
|
||||
end
|
||||
|
||||
def environments_detail_data_json(user, project, environment)
|
||||
environments_detail_data(user, project, environment).to_json
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -66,17 +66,14 @@ module Gitlab
|
|||
link_to project_blame_path(project, tree_join(previous_commit_id, path)),
|
||||
title: _('View blame prior to this change'),
|
||||
aria: { label: _('View blame prior to this change') },
|
||||
class: 'version-link',
|
||||
data: { toggle: 'tooltip', placement: 'right', container: 'body' } do
|
||||
versions_sprite_icon
|
||||
' '.html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def project_duration
|
||||
@project_duration ||= age_map_duration(groups, project)
|
||||
end
|
||||
|
||||
def versions_sprite_icon
|
||||
@versions_sprite_icon ||= sprite_icon('doc-versions', css_class: 'doc-versions align-text-bottom')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
- page_title _('Runners')
|
||||
|
||||
- if Feature.enabled?(:runner_list_view_vue_ui, current_user, default_enabled: :yaml)
|
||||
#js-runner-list{ data: { registration_token: Gitlab::CurrentSettings.runners_registration_token, runner_install_help_page: 'https://docs.gitlab.com/runner/install/', active_runners_count: @active_runners_count } }
|
||||
#js-admin-runners{ data: { registration_token: Gitlab::CurrentSettings.runners_registration_token, runner_install_help_page: 'https://docs.gitlab.com/runner/install/', active_runners_count: @active_runners_count } }
|
||||
- else
|
||||
.row
|
||||
.col-sm-6
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
- if environment.external_url && can?(current_user, :read_environment, environment)
|
||||
= link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'gl-button btn external-url has-tooltip qa-view-deployment', title: s_('Environments|Open live environment') do
|
||||
= sprite_icon('external-link')
|
||||
= _("View deployment")
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
- environment = local_assigns.fetch(:environment)
|
||||
|
||||
- return unless can?(current_user, :read_environment, environment)
|
||||
|
||||
= link_to environment_metrics_path(environment), title: _('See metrics'), class: 'gl-button btn metrics-button' do
|
||||
= sprite_icon('chart', css_class: 'gl-mr-2')
|
||||
= _("Monitoring")
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
- if environment.auto_stop_at? && environment.available?
|
||||
= button_to cancel_auto_stop_project_environment_path(environment.project, environment), class: 'gl-button btn btn-secondary has-tooltip', title: _('Prevent environment from auto-stopping') do
|
||||
= sprite_icon('thumbtack')
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
|
||||
= link_to terminal_project_environment_path(@project, environment), class: 'gl-button btn terminal-button' do
|
||||
= sprite_icon('terminal')
|
||||
|
|
@ -5,58 +5,8 @@
|
|||
- add_page_specific_style 'page_bundles/environments'
|
||||
- add_page_specific_style 'page_bundles/ci_status'
|
||||
|
||||
#environments-detail-view{ data: { name: @environment.name, id: @environment.id, delete_path: environment_delete_path(@environment)} }
|
||||
- if @environment.available? && can?(current_user, :stop_environment, @environment)
|
||||
#stop-environment-modal.modal.fade{ tabindex: -1 }
|
||||
.modal-dialog
|
||||
.modal-content
|
||||
.modal-header
|
||||
%h4.modal-title.d-flex.mw-100
|
||||
= s_("Environments|Stopping")
|
||||
%span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } }
|
||||
#{@environment.name}?
|
||||
.modal-body
|
||||
%p= s_('Environments|Are you sure you want to stop this environment?')
|
||||
- unless @environment.stop_action_available?
|
||||
.warning_message
|
||||
%p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe,
|
||||
emphasis_end: '</strong>'.html_safe,
|
||||
ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe,
|
||||
ci_config_link_end: '</a>'.html_safe }
|
||||
%a{ href: 'https://docs.gitlab.com/ee/ci/environments/index.html#stopping-an-environment',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer' }
|
||||
= s_('Environments|Learn more about stopping environments')
|
||||
.modal-footer
|
||||
= button_tag _('Cancel'), type: 'button', class: 'gl-button btn btn-cancel', data: { dismiss: 'modal' }
|
||||
= button_to stop_project_environment_path(@project, @environment), class: 'gl-button btn btn-danger has-tooltip', method: :post do
|
||||
= s_('Environments|Stop environment')
|
||||
|
||||
- if can_destroy_environment?(@environment)
|
||||
#delete-environment-modal
|
||||
|
||||
.top-area.justify-content-between
|
||||
.d-flex
|
||||
%h3.page-title= @environment.name
|
||||
- if @environment.auto_stop_at?
|
||||
%p.align-self-end.gl-ml-3
|
||||
= s_('Environments|Auto stops %{auto_stop_time}').html_safe % {auto_stop_time: time_ago_with_tooltip(@environment.auto_stop_at)}
|
||||
.nav-controls.my-2
|
||||
= render 'projects/environments/pin_button', environment: @environment
|
||||
= render 'projects/environments/terminal_button', environment: @environment
|
||||
= render 'projects/environments/external_url', environment: @environment
|
||||
= render 'projects/environments/metrics_button', environment: @environment
|
||||
- if can?(current_user, :update_environment, @environment)
|
||||
= link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn'
|
||||
- if @environment.available? && can?(current_user, :stop_environment, @environment)
|
||||
= button_tag class: 'gl-button btn btn-danger', type: 'button', data: { toggle: 'modal',
|
||||
target: '#stop-environment-modal' } do
|
||||
= sprite_icon('stop')
|
||||
= s_('Environments|Stop')
|
||||
- if can_destroy_environment?(@environment)
|
||||
= button_tag class: 'gl-button btn btn-danger', type: 'button', data: { toggle: 'modal',
|
||||
target: '#delete-environment-modal' } do
|
||||
= s_('Environments|Delete')
|
||||
#environments-detail-view{ data: { details: environments_detail_data_json(current_user, @project, @environment) } }
|
||||
#environments-detail-view-header
|
||||
|
||||
.environments-container
|
||||
- if @deployments.blank?
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: runner_registration_control
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61407
|
||||
rollout_issue_url:
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336087
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::runner
|
||||
|
|
|
|||
|
|
@ -74,6 +74,32 @@ GitLab sidebar:
|
|||
|
||||
GitLab displays your link in the **Menu > Admin > Monitoring > Metrics Dashboard**.
|
||||
|
||||
## Required Scopes
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5822) in GitLab 13.10.
|
||||
|
||||
When setting up Grafana through the process above, no scope shows in the screen at
|
||||
**Menu >** **{admin}** **Admin > Applications > GitLab Grafana**. However, the `read_user` scope is
|
||||
required and is provided to the application automatically. Note that setting any scope other than
|
||||
`read_user` without also including `read_user` leads to this error when you try to log in using
|
||||
GitLab as the OAuth provider:
|
||||
|
||||
```plaintext
|
||||
The requested scope is invalid, unknown, or malformed.
|
||||
```
|
||||
|
||||
If you see this error, make sure that one of the following is true in the GitLab Grafana
|
||||
configuration screen:
|
||||
|
||||
- No scopes appear.
|
||||
- The `read_user` scope is included.
|
||||
|
||||
> Versions of GitLab prior 13.10 use the API scope instead of `read_user`. In versions of GitLab
|
||||
> prior to 13.10, the API scope:
|
||||
>
|
||||
> - Is required to access Grafana through the GitLab OAuth provider.
|
||||
> - Is set by enabling the Grafana application as shown in [Integration with GitLab UI](#integration-with-gitlab-ui).
|
||||
|
||||
## Security Update
|
||||
|
||||
Users running GitLab version 12.0 or later should immediately upgrade to one of the
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
stage: Fulfillment
|
||||
group: Purchase
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# The quarterly subscription reconciliation process
|
||||
|
||||
GitLab reviews your seat usage every quarter and sends you an invoice for
|
||||
any overages.
|
||||
|
||||
Annual reviews, also known as the [annual true-up process](self_managed/index.md#users-over-license),
|
||||
require you to pay the full annual subscription fee for users added anytime during the year. With quarterly
|
||||
reviews, you only pay for the remaining period of your subscription term.
|
||||
|
||||
For example, if you add users in the third quarter of your subscription term, you only
|
||||
pay 25% of what you would have paid previously. This results in substantial savings.
|
||||
|
||||
If it's not possible for you to participate in quarterly reconciliations, you can opt out of the
|
||||
process by using a contract amendment. In that case, you default to the annual review.
|
||||
|
|
@ -39,9 +39,11 @@ for each tier, see the
|
|||
|
||||
A GitLab self-managed subscription uses a hybrid model. You pay for a subscription
|
||||
according to the maximum number of users enabled during the subscription period.
|
||||
For instances that are offline or on a closed network, the maximum number of
|
||||
simultaneous users in the GitLab self-managed installation is checked each quarter,
|
||||
using [Seat Link](#seat-link).
|
||||
For instances that aren't offline or on a closed network, the maximum number of
|
||||
simultaneous users in the GitLab self-managed installation is checked each quarter.
|
||||
|
||||
If an instance is unable to generate a quarterly usage report, the existing [true-up model](#users-over-license) is used.
|
||||
Prorated charges are not possible without a quarterly usage report.
|
||||
|
||||
### Billable users
|
||||
|
||||
|
|
@ -77,6 +79,140 @@ GitLab has several features which can help you manage the number of users:
|
|||
users manually.
|
||||
- View a breakdown of users by role in the [Users statistics](../../user/admin_area/index.md#users-statistics) page.
|
||||
|
||||
## Cloud licensing
|
||||
|
||||
> Introduced in GitLab 14.1.
|
||||
|
||||
Cloud licensing manages licenses for self-managed GitLab subscription plans. Cloud licensing includes:
|
||||
|
||||
- Activation: Unlock plan features and activate your self-managed instance by using an activation code.
|
||||
- License sync: Sync subscription data between your self-managed instance and GitLab.
|
||||
|
||||
### What cloud licensing includes
|
||||
|
||||
#### Auto-renewals
|
||||
|
||||
For renewals that occur on or after 2021-08-01, your subscriptions will auto-renew.
|
||||
You have the option to manually cancel in the Customers Portal any time until thirty (30) days before renewal.
|
||||
|
||||
#### Cloud licensing
|
||||
|
||||
You can activate and manage your GitLab licenses by using the Customers Portal.
|
||||
This feature was formerly known as Seat Link.
|
||||
|
||||
#### Operational data
|
||||
|
||||
Usage data helps GitLab improve the product experience and provide proactive support.
|
||||
Most data is categorized as optional and can be disabled. Data that is categorized as
|
||||
operational, like number of issues, pipelines, merge requests, and version, is not configurable.
|
||||
|
||||
Please see our [service usage privacy page](https://about.gitlab.com/handbook/legal/privacy/services-usage-data/)
|
||||
for details on what information is collected.
|
||||
|
||||
#### Quarterly subscription reconciliation
|
||||
|
||||
See the [quarterly subscription reconciliation section](../quarterly_reconciliation.md) for more information.
|
||||
|
||||
### How cloud licensing works
|
||||
|
||||
#### Activate your license
|
||||
|
||||
1. When you purchase a GitLab self-managed plan, an activation code is generated.
|
||||
This activation code is sent to the email address associated with the Customers Portal account.
|
||||
1. In GitLab, on the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. On the left sidebar, select **Subscription** and paste the activation code in the text field.
|
||||
1. Select **Activate**.
|
||||
|
||||
The page displays the details of the subscription.
|
||||
|
||||
#### License sync
|
||||
|
||||
Once a day, a job sends license data to the Customers Portal. This information automates activation,
|
||||
provisioning, co-terms, and renewals. The data is sent securely through an encrypted HTTPS connection
|
||||
to `customers.gitlab.com` on port `443`.
|
||||
|
||||
This sync job runs daily around 3AM UTC. If the job fails, it is retried up to 12 times over approximately 17 hours.
|
||||
|
||||
The daily job provides **only** the following information to the Customers Portal:
|
||||
|
||||
- Date
|
||||
- Timestamp
|
||||
- License key
|
||||
- Historical maximum user count
|
||||
- Billable users count
|
||||
- GitLab version
|
||||
- Hostname
|
||||
- Instance ID
|
||||
- MD5 hash of license
|
||||
|
||||
<details>
|
||||
<summary>Click here to view an example of a cloud licensing sync request.</summary>
|
||||
<pre><code>
|
||||
{
|
||||
"gitlab_version": "14.1.0-pre",
|
||||
"timestamp": "2021-06-14T12:00:09Z",
|
||||
"date": "2021-06-14",
|
||||
"license_key": "eyJkYXRhIjoiYlR2MFBPSEJPSnNOc1plbGtFRGZ6M
|
||||
Ex1mWWhyM1Y3NWFOU0Zj\nak1xTmtLZHU1YzJJUWJzZzVxT3FQRU1PXG5
|
||||
KRzErL2ZNd0JuKzBwZmQ3YnY4\nTkFrTDFsMFZyQi9NcG5DVEdkTXQyNT
|
||||
R3NlR0ZEc0MjBoTTVna2VORlVcbjAz\nbUgrNGl5N0NuenRhZlljd096R
|
||||
nUzd2JIWEZ3NzV2V2lqb3FuQ3RYZWppWVFU\neDdESkgwSUIybFJhZlxu
|
||||
Y2k0Mzl3RWlKYjltMkJoUzExeGIwWjN3Uk90ZGp1\nNXNNT3dtL0Vtc3l
|
||||
zWVowSHE3ekFILzBjZ2FXSXVQXG5ENWJwcHhOZzRlcFhr\neFg0K3d6Zk
|
||||
w3cHRQTTJMTGdGb2Vwai90S0VJL0ZleXhxTEhvaUc2NzVIbHRp\nVlRcb
|
||||
nYzY090bmhsdTMrc0VGZURJQ3VmcXFFUS9ISVBqUXRhL3ZTbW9SeUNh\n
|
||||
SjdDTkU4YVJnQTlBMEF5OFBiZlxuT0VORWY5WENQVkREdUMvTTVCb25Re
|
||||
ENv\nK0FrekFEWWJ6VGZLZ1dBRjgzUXhyelJWUVJGTTErWm9TeTQ4XG5V
|
||||
aWdXV0d4\nQ2graGtoSXQ1eXdTaUFaQzBtZGd2aG1YMnl1KzltcU9WMUx
|
||||
RWXE4a2VSOHVn\nV3BMN1VFNThcbnMvU3BtTk1JZk5YUHhOSmFlVHZqUz
|
||||
lXdjlqMVZ6ODFQQnFx\nL1phaTd6MFBpdG5NREFOVnpPK3h4TE5CQ1xub
|
||||
GtacHNRdUxTZmtWWEZVUnB3\nWTZtWGdhWE5GdXhURjFndWhyVDRlTE92
|
||||
bTR3bW1ac0pCQnBkVWJIRGNyXG5z\nUjVsTWJxZEVUTXJNRXNDdUlWVlZ
|
||||
CTnJZVTA2M2dHblc4eVNXZTc0enFUcW1V\nNDBrMUZpN3RTdzBaZjBcbm
|
||||
16UGNYV0RoelpkVk02cWR1dTl0Q1VqU05tWWlU\nOXlwRGZFaEhXZWhjb
|
||||
m50RzA5UWVjWEM5em52Y1BjU1xueFU0MDMvVml5R3du\nQXNMTHkyajN5
|
||||
b3hhTkJUSWpWQ1BMUjdGeThRSEVnNGdBd0x6RkRHVWg1M0Qz\nMHFRXG5
|
||||
5eWtXdHNHN3VBREdCNmhPODFJanNSZnEreDhyb2ZpVU5JVXo4NCtD\nem
|
||||
Z1V1Q0K1l1VndPTngyc1l0TU5cbi9WTzlaaVdPMFhtMkZzM2g1NlVXcGI
|
||||
y\nSUQzRnRlbW5vZHdLOWU4L0tiYWRESVRPQmgzQnIxbDNTS2tHN1xuQ3
|
||||
hpc29D\nNGh4UW5mUmJFSmVoQkh6eHV1dkY5aG11SUsyVmVDQm1zTXZCY
|
||||
nZQNGdDbHZL\ndUExWnBEREpDXG41eEhEclFUd3E1clRYS2VuTjhkd3BU
|
||||
SnVLQXgvUjlQVGpy\ncHJLNEIzdGNMK0xIN2JKcmhDOTlabnAvLzZcblZ
|
||||
HbXk5SzJSZERIcXp3U2c3\nQjFwSmFPcFBFUHhOUFJxOUtnY2hVR0xWMF
|
||||
d0Rk9vPVxuIiwia2V5IjoiUURM\nNU5paUdoRlVwZzkwNC9lQWg5bFY0Q
|
||||
3pkc2tSQjBDeXJUbG1ZNDE2eEpPUzdM\nVXkrYXRhTFdpb0lTXG5sTWlR
|
||||
WEU3MVY4djFJaENnZHJGTzJsTUpHbUR5VHY0\ndWlSc1FobXZVWEhpL3h
|
||||
vb1J4bW9XbzlxK2Z1OGFcblB6anp1TExhTEdUQVdJ\nUDA5Z28zY3JCcz
|
||||
ZGOEVLV28xVzRGWWtUUVh2TzM0STlOSjVHR1RUeXkzVkRB\nc1xubUdRe
|
||||
jA2eCtNNkFBM1VxTUJLZXRMUXRuNUN2R3l3T1VkbUx0eXZNQ3JX\nSWVQ
|
||||
TElrZkJwZHhPOUN5Z1dCXG44UkpBdjRSQ1dkMlFhWVdKVmxUMllRTXc5\
|
||||
nL29LL2hFNWRQZ1pLdWEyVVZNRWMwRkNlZzg5UFZrQS9mdDVcbmlETWlh
|
||||
YUZz\nakRVTUl5SjZSQjlHT2ovZUdTRTU5NVBBMExKcFFiVzFvZz09XG4
|
||||
iLCJpdiI6\nImRGSjl0YXlZWit2OGlzbGgyS2ZxYWc9PVxuIn0=\n",
|
||||
"max_historical_user_count": 75,
|
||||
"billable_users_count": 75,
|
||||
"hostname": "gitlab.example.com",
|
||||
"instance_id": "9367590b-82ad-48cb-9da7-938134c29088",
|
||||
"license_md5": "002f02470fe45ef6a333a4282aca6222"
|
||||
}
|
||||
</code></pre>
|
||||
</details>
|
||||
|
||||
#### Sync subscription details
|
||||
|
||||
You can manually sync your subscription details at any time.
|
||||
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. On the left sidebar, select **Subscription**.
|
||||
1. In the **Subscription details** section, select **Sync subscription details**.
|
||||
|
||||
A job is queued. When the job finishes, the subscription details are updated.
|
||||
|
||||
#### Troubleshooting cloud licensing sync
|
||||
|
||||
If the sync job is not working, ensure you allow network traffic from your GitLab instance
|
||||
to IP address `104.46.106.135:443` (`customers.gitlab.com`).
|
||||
|
||||
## Obtain a subscription
|
||||
|
||||
To subscribe to GitLab through a GitLab self-managed installation:
|
||||
|
|
@ -201,117 +337,6 @@ We recommend following these steps during renewal:
|
|||
|
||||
An invoice is generated for the renewal and available for viewing or download on the [View invoices](https://customers.gitlab.com/receipts) page. If you have difficulty during the renewal process, contact our [support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) for assistance.
|
||||
|
||||
### Seat Link
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/208832) in GitLab 12.9.
|
||||
|
||||
Seat Link allows GitLab Inc. to provide our GitLab self-managed customers with prorated charges for user growth throughout the year using a quarterly reconciliation process.
|
||||
|
||||
Seat Link daily sends a count of all users in connected GitLab self-managed instances to GitLab. That information is used to automate prorated reconciliations. The data is sent securely through an encrypted HTTPS connection to `customers.gitlab.com` on port `443`.
|
||||
|
||||
Seat Link provides **only** the following information to GitLab:
|
||||
|
||||
- Date
|
||||
- Timestamp
|
||||
- License key
|
||||
- Historical maximum user count
|
||||
- Billable users count
|
||||
- GitLab version
|
||||
- Hostname
|
||||
- Instance ID
|
||||
- MD5 hash of license
|
||||
|
||||
For offline or closed network customers, the existing [true-up model](#users-over-license) is used. Prorated charges are not possible without user count data.
|
||||
|
||||
<details>
|
||||
<summary>Click here to view example content of a Seat Link POST request.</summary>
|
||||
|
||||
<pre><code>
|
||||
{
|
||||
gitlab_version: '13.12.0',
|
||||
timestamp: '2020-01-29T18:25:57+00:00',
|
||||
date: '2020-01-29',
|
||||
license_key: 'ZXlKa1lYUmhJam9pWm5WNmVsTjVZekZ2YTJoV2NucDBh
|
||||
RXRxTTA5amQxcG1VMVZqDQpXR3RwZEc5SGIyMVhibmxuZDJ0NWFrNXJTVzVH
|
||||
UzFCT1hHNVRiVFIyT0ZaUFlVSm1OV1ZGV0VObE1uVk4NCk4xY3ZkM1F4Y2to
|
||||
MFFuVklXSFJvUWpSM01VdE9SVE5rYkVjclZrdDJORkpOTlhka01qaE5aalpj
|
||||
YmxSMg0KWVd3MFNFTldTRmRtV1ZGSGRDOUhPR05oUVZvNUsxVnRXRUZIZFU1
|
||||
U1VqUm5aVFZGZUdwTWIxbDFZV1EyDQphV1JTY1V4c1ZYSjNPVGhrYVZ4dVlu
|
||||
TkpWMHRJZUU5dmF6ZEJRVVkxTlVWdFUwMTNSMGRHWm5SNlJFcFYNClQyVkJl
|
||||
VXc0UzA0NWFFb3ZlSFJrZW0xbVRqUlZabkZ4U1hWcWNXRnZYRzVaTm5GSmVW
|
||||
UnJVR1JQYTJKdA0KU0ZZclRHTmFPRTVhZEVKMUt6UjRkSE15WkRCT1UyNWlS
|
||||
MGRJZDFCdmRFWk5Za2h4Tm5sT1VsSktlVlYyDQpXRmhjYmxSeU4wRnRNMU5q
|
||||
THpCVWFGTmpTMnh3UWpOWVkyc3pkbXBST1dnelZHY3hUV3hxVDIwdlZYRlQN
|
||||
Ck9EWTJSVWx4WlVOT01EQXhVRlZ3ZGs1Rk0xeHVSVEJTTDFkMWJUQTVhV1ZK
|
||||
WjBORFdWUktaRXNyVnpsTw0KTldkWWQwWTNZa05VWlZBMmRUVk9kVUpxT1hV
|
||||
Mk5VdDFTUzk0TUU5V05XbFJhWGh0WEc1cVkyWnhaeTlXDQpTMEpyZWt0cmVY
|
||||
bzBOVGhFVG1oU1oxSm5WRFprY0Uwck0wZEdhVUpEV1d4a1RXZFRjVU5tYTB0
|
||||
a2RteEQNCmNWTlFSbFpuWlZWY2JpdFVVbXhIV0d4MFRuUnRWbkJKTkhwSFJt
|
||||
TnRaMGsyV0U1MFFUUXJWMUJVTWtOSA0KTVhKUWVGTkxPVTkzV1VsMlVUUldk
|
||||
R3hNTWswNU1USlNjRnh1U1UxTGJTdHRRM1l5YTFWaWJtSlBTMkUxDQplRkpL
|
||||
SzJSckszaG1hVXB1ZVRWT1UwdHZXV0ZOVG1WamMyVjRPV0pSUlZkUU9UUnpU
|
||||
VWh2Wlc5cFhHNUgNClNtRkdVMDUyY1RGMWNGTnhVbU5JUkZkeGVWcHVRMnBh
|
||||
VTBSUGR6VnRNVGhvWTFBM00zVkZlVzFOU0djMA0KY1ZFM1FWSlplSFZ5UzFS
|
||||
aGIxTmNia3BSUFQxY2JpSXNJbxRsZVNJNkltZFhiVzFGVkRZNWNFWndiV2Rt
|
||||
DQpNWEIyY21SbFFrdFNZamxaYURCdVVHcHhiRlV3Tm1WQ2JGSlFaSFJ3Y0Rs
|
||||
cFMybGhSMnRPTkZOMWNVNU0NClVGeHVTa3N6TUUxcldVOTVWREl6WVVWdk5U
|
||||
ZGhWM1ZvVjJkSFRtZFBZVXRJTkVGcE55dE1NRE5dWnpWeQ0KWlV0aWJsVk9T
|
||||
RmRzVVROUGRHVXdWR3hEWEc1MWjWaEtRMGQ2YTAxWFpUZHJURTVET0doV00w
|
||||
ODRWM0V2DQphV2M1YWs5cWFFWk9aR3BYTm1aVmJXNUNaazlXVUVRMWRrMXpj
|
||||
bTFDV0V4dldtRmNibFpTTWpWU05VeFMNClEwTjRNMWxWCUtSVGEzTTJaV2xE
|
||||
V0hKTFRGQmpURXRsZFVaQlNtRnJTbkpPZGtKdlUyUmlNVWxNWWpKaQ0KT0dw
|
||||
c05YbE1kVnh1YzFWbk5VZDFhbU56ZUM5Tk16TXZUakZOVW05cVpsVTNObEo0
|
||||
TjJ4eVlVUkdkWEJtDQpkSHByYWpreVJrcG9UVlo0Y0hKSU9URndiV2RzVFdO
|
||||
VlhHNXRhVmszTkV0SVEzcEpNMWRyZEVoRU4ydHINCmRIRnFRVTlCVUVVM1pV
|
||||
SlRORE4xUjFaYVJGb3JlWGM5UFZ4dUlpd2lhWFlpt2lKV00yRnNVbk5RTjJk
|
||||
Sg0KU1hNMGExaE9SVGR2V2pKQlBUMWNiaUo5DQo=',
|
||||
hostname: 'gitlab.example.com',
|
||||
instance_id: 'c1ac02cb-cb3f-4120-b7fe-961bbfa3abb7',
|
||||
license_md5: '7cd897fffb3517dddf01b79a0889b515'
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
</details>
|
||||
|
||||
You can view the exact JSON payload in the administration panel. To view the payload:
|
||||
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. On the left sidebar, select **Settings > Metrics and profiling** and expand **Seat Link**.
|
||||
1. Select **Preview payload**.
|
||||
|
||||
#### Disable Seat Link
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/212375) in GitLab 12.10.
|
||||
|
||||
Seat Link is enabled by default.
|
||||
|
||||
To disable this feature:
|
||||
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. On the left sidebar, select **Settings > Metrics and profiling** and expand **Seat Link**.
|
||||
1. Clear the **Enable Seat Link** checkbox.
|
||||
1. Select **Save changes**.
|
||||
|
||||
To disable Seat Link in an Omnibus GitLab installation, and prevent it from
|
||||
being configured in the future through the administration panel, set the following in
|
||||
[`gitlab.rb`](https://docs.gitlab.com/omnibus/settings/configuration.html#configuration-options):
|
||||
|
||||
```ruby
|
||||
gitlab_rails['seat_link_enabled'] = false
|
||||
```
|
||||
|
||||
To disable Seat Link in a GitLab source installation, and prevent it from
|
||||
being configured in the future through the administration panel,
|
||||
set the following in `gitlab.yml`:
|
||||
|
||||
```yaml
|
||||
production: &base
|
||||
# ...
|
||||
gitlab:
|
||||
# ...
|
||||
seat_link_enabled: false
|
||||
```
|
||||
|
||||
## Upgrade your subscription tier
|
||||
|
||||
To upgrade your [GitLab tier](https://about.gitlab.com/pricing/):
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ feature is available.
|
|||
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#disable-or-enable-devops-adoption). **(ULTIMATE SELF)**
|
||||
> - The Overview tab [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330401) in GitLab 14.1.
|
||||
> - DAST and SAST metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/328033) in GitLab 14.1.
|
||||
> - Fuzz Testing metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/330398) in GitLab 14.2.
|
||||
|
||||
DevOps Adoption shows you which groups in your organization are using the most essential features of GitLab:
|
||||
|
||||
|
|
@ -57,6 +58,7 @@ DevOps Adoption shows you which groups in your organization are using the most e
|
|||
- Sec
|
||||
- DAST
|
||||
- SAST
|
||||
- Fuzz Testing
|
||||
- Ops
|
||||
- Deployments
|
||||
- Pipelines
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/333556) in GitLab 14.1.
|
||||
> - The Overview tab [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330401) in GitLab 14.1.
|
||||
> - DAST and SAST metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/328033) in GitLab 14.1.
|
||||
> - Fuzz Testing metrics [added](https://gitlab.com/gitlab-org/gitlab/-/issues/330398) in GitLab 14.2.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ Group DevOps Adoption shows you how individual groups and sub-groups within your
|
|||
- Sec
|
||||
- DAST
|
||||
- SAST
|
||||
- Fuzz Testing
|
||||
- Ops
|
||||
- Deployments
|
||||
- Pipelines
|
||||
|
|
|
|||
|
|
@ -11267,6 +11267,12 @@ msgstr ""
|
|||
msgid "DevopsAdoption|Feature adoption is based on usage in the previous calendar month. Last updated: %{timestamp}."
|
||||
msgstr ""
|
||||
|
||||
msgid "DevopsAdoption|Fuzz Testing"
|
||||
msgstr ""
|
||||
|
||||
msgid "DevopsAdoption|Fuzz Testing enabled for at least one project"
|
||||
msgstr ""
|
||||
|
||||
msgid "DevopsAdoption|Issues"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -12445,7 +12451,7 @@ msgstr ""
|
|||
msgid "Environments|Auto stop in"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Auto stops %{auto_stop_time}"
|
||||
msgid "Environments|Auto stops %{autoStopAt}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Commit"
|
||||
|
|
@ -12526,9 +12532,6 @@ msgstr ""
|
|||
msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file."
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Open live environment"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -12571,9 +12574,6 @@ msgstr ""
|
|||
msgid "Environments|Stop environment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Stopping"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Stopping %{environmentName}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
1
qa/qa.rb
1
qa/qa.rb
|
|
@ -385,7 +385,6 @@ module QA
|
|||
module Deployments
|
||||
module Environments
|
||||
autoload :Index, 'qa/page/project/deployments/environments/index'
|
||||
autoload :Show, 'qa/page/project/deployments/environments/show'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Page
|
||||
module Project
|
||||
module Deployments
|
||||
module Environments
|
||||
class Show < Page::Base
|
||||
view 'app/views/projects/environments/_external_url.html.haml' do
|
||||
element :view_deployment
|
||||
end
|
||||
|
||||
def view_deployment(&block)
|
||||
new_window = window_opened_by { click_element(:view_deployment) }
|
||||
|
||||
within_window(new_window, &block) if block
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -41,7 +41,7 @@ module QA
|
|||
|
||||
after do
|
||||
runner.remove_via_api!
|
||||
group.remove_via_api!
|
||||
[upstream_project, downstream_project].each(&:remove_via_api!)
|
||||
end
|
||||
|
||||
it 'runs the pipeline with composed config', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1086' do
|
||||
|
|
|
|||
|
|
@ -20,22 +20,20 @@ class StaticAnalysis
|
|||
# contain values that a FOSS installation won't find. To work
|
||||
# around this we will only enable this task on EE installations.
|
||||
TASKS_BY_DURATIONS_SECONDS_DESC = {
|
||||
%w[bin/rake lint:haml] => 488,
|
||||
(Gitlab.ee? ? %w[bin/rake gettext:updated_check] : nil) => 410,
|
||||
# Most of the time, RuboCop finishes in 30 seconds, but sometimes it can take around 1200 seconds so we set a
|
||||
# duration of 300 to lower the likelihood that it will run in the same job as another long task...
|
||||
%w[bundle exec rubocop --parallel --except Gitlab/MarkUsedFeatureFlags] => 300,
|
||||
%w[bin/rake lint:haml] => 800,
|
||||
# We need to disable the cache for this cop since it creates files under tmp/feature_flags/*.used,
|
||||
# the cache would prevent these files from being created.
|
||||
%w[bundle exec rubocop --only Gitlab/MarkUsedFeatureFlags --cache false] => 600,
|
||||
%w[yarn run lint:eslint:all] => 264,
|
||||
%w[yarn run lint:prettier] => 134,
|
||||
%w[bin/rake gettext:lint] => 81,
|
||||
%w[bundle exec license_finder] => 49,
|
||||
%w[bin/rake lint:static_verification] => 24,
|
||||
%w[bin/rake gitlab:sidekiq:all_queues_yml:check] => 12,
|
||||
(Gitlab.ee? ? %w[bin/rake gettext:updated_check] : nil) => 360,
|
||||
%w[yarn run lint:eslint:all] => 312,
|
||||
%w[yarn run lint:prettier] => 162,
|
||||
%w[bin/rake gettext:lint] => 65,
|
||||
%w[bundle exec license_finder] => 61,
|
||||
%w[bin/rake lint:static_verification] => 45,
|
||||
%w[bundle exec rubocop --parallel] => 40,
|
||||
%w[bin/rake config_lint] => 26,
|
||||
%w[bin/rake gitlab:sidekiq:all_queues_yml:check] => 15,
|
||||
(Gitlab.ee? ? %w[bin/rake gitlab:sidekiq:sidekiq_queues_yml:check] : nil) => 11,
|
||||
%w[bin/rake config_lint] => 11,
|
||||
%w[yarn run internal:stylelint] => 8,
|
||||
%w[scripts/lint-conflicts.sh] => 1,
|
||||
%w[yarn run block-dependencies] => 1,
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ module Trigger
|
|||
{
|
||||
'GITLAB_VERSION' => source_sha,
|
||||
'IMAGE_TAG' => source_sha,
|
||||
'QA_IMAGE' => "#{ENV['CI_REGISTRY']}/#{ENV['CI_PROJECT_PATH']}/gitlab-ee-qa:#{ENV['CI_COMMIT_REF_SLUG']}",
|
||||
'QA_IMAGE' => ENV['QA_IMAGE'],
|
||||
'SKIP_QA_DOCKER' => 'true',
|
||||
'ALTERNATIVE_SOURCES' => 'true',
|
||||
'SECURITY_SOURCES' => Trigger.security? ? 'true' : 'false',
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ RSpec.describe 'Environment > Metrics' do
|
|||
|
||||
shared_examples 'has environment selector' do
|
||||
it 'has a working environment selector', :js do
|
||||
click_link('See metrics')
|
||||
click_link 'Monitoring'
|
||||
|
||||
expect(page).to have_current_path(project_metrics_dashboard_path(project, environment: environment.id))
|
||||
expect(page).to have_css('[data-qa-selector="environments_dropdown"]')
|
||||
|
|
@ -55,10 +55,10 @@ RSpec.describe 'Environment > Metrics' do
|
|||
create(:deployment, environment: environment, deployable: build)
|
||||
end
|
||||
|
||||
it 'shows metrics' do
|
||||
click_link('See metrics')
|
||||
it 'shows metrics', :js do
|
||||
click_link 'Monitoring'
|
||||
|
||||
expect(page).to have_css('div#prometheus-graphs')
|
||||
expect(page).to have_css('[data-qa-selector="prometheus_graphs"]')
|
||||
end
|
||||
|
||||
it_behaves_like 'has environment selector'
|
||||
|
|
|
|||
|
|
@ -27,20 +27,6 @@ RSpec.describe 'Environment' do
|
|||
visit_environment(environment)
|
||||
end
|
||||
|
||||
it 'shows environment name' do
|
||||
expect(page).to have_content(environment.name)
|
||||
end
|
||||
|
||||
context 'without auto-stop' do
|
||||
it 'does not show auto-stop text' do
|
||||
expect(page).not_to have_content('Auto stops')
|
||||
end
|
||||
|
||||
it 'does not show auto-stop button' do
|
||||
expect(page).not_to have_selector(auto_stop_button_selector)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with auto-stop' do
|
||||
let!(:environment) { create(:environment, :will_auto_stop, name: 'staging', project: project) }
|
||||
|
||||
|
|
@ -48,11 +34,11 @@ RSpec.describe 'Environment' do
|
|||
visit_environment(environment)
|
||||
end
|
||||
|
||||
it 'shows auto stop info' do
|
||||
it 'shows auto stop info', :js do
|
||||
expect(page).to have_content('Auto stops')
|
||||
end
|
||||
|
||||
it 'shows auto stop button' do
|
||||
it 'shows auto stop button', :js do
|
||||
expect(page).to have_selector(auto_stop_button_selector)
|
||||
expect(page.find(auto_stop_button_selector).find(:xpath, '..')['action']).to have_content(cancel_auto_stop_project_environment_path(environment.project, environment))
|
||||
end
|
||||
|
|
@ -80,7 +66,6 @@ RSpec.describe 'Environment' do
|
|||
it 'does show deployment SHA' do
|
||||
expect(page).to have_link(deployment.short_sha)
|
||||
expect(page).not_to have_link('Re-deploy')
|
||||
expect(page).not_to have_terminal_button
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -186,7 +171,7 @@ RSpec.describe 'Environment' do
|
|||
let(:build) { create(:ci_build, pipeline: pipeline) }
|
||||
let(:deployment) { create(:deployment, :success, environment: environment, deployable: build) }
|
||||
|
||||
it 'does show an external link button' do
|
||||
it 'does show an external link button', :js do
|
||||
expect(page).to have_link(nil, href: environment.external_url)
|
||||
end
|
||||
end
|
||||
|
|
@ -200,10 +185,6 @@ RSpec.describe 'Environment' do
|
|||
context 'for project maintainer' do
|
||||
let(:role) { :maintainer }
|
||||
|
||||
it 'shows the terminal button' do
|
||||
expect(page).to have_terminal_button
|
||||
end
|
||||
|
||||
context 'web terminal', :js do
|
||||
before do
|
||||
# Stub #terminals as it causes js-enabled feature specs to
|
||||
|
|
@ -224,14 +205,6 @@ RSpec.describe 'Environment' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for developer' do
|
||||
let(:role) { :developer }
|
||||
|
||||
it 'does not show terminal button' do
|
||||
expect(page).not_to have_terminal_button
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -259,7 +232,7 @@ RSpec.describe 'Environment' do
|
|||
click_button('Stop')
|
||||
click_button('Stop environment') # confirm modal
|
||||
wait_for_all_requests
|
||||
expect(page).to have_content('close_app')
|
||||
expect(page).to have_button('Delete')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -269,7 +242,7 @@ RSpec.describe 'Environment' do
|
|||
name: action.ref, project: project)
|
||||
end
|
||||
|
||||
it 'does not allow to stop environment' do
|
||||
it 'does not allow to stop environment', :js do
|
||||
expect(page).not_to have_button('Stop')
|
||||
end
|
||||
end
|
||||
|
|
@ -277,7 +250,7 @@ RSpec.describe 'Environment' do
|
|||
context 'for reporter' do
|
||||
let(:role) { :reporter }
|
||||
|
||||
it 'does not show stop button' do
|
||||
it 'does not show stop button', :js do
|
||||
expect(page).not_to have_button('Stop')
|
||||
end
|
||||
end
|
||||
|
|
@ -287,7 +260,7 @@ RSpec.describe 'Environment' do
|
|||
context 'when environment is stopped' do
|
||||
let(:environment) { create(:environment, project: project, state: :stopped) }
|
||||
|
||||
it 'does not show stop button' do
|
||||
it 'does not show stop button', :js do
|
||||
expect(page).not_to have_button('Stop')
|
||||
end
|
||||
end
|
||||
|
|
@ -323,7 +296,7 @@ RSpec.describe 'Environment' do
|
|||
ref: 'feature')
|
||||
end
|
||||
|
||||
it 'user visits environment page' do
|
||||
it 'user visits environment page', :js do
|
||||
visit_environment(environment)
|
||||
|
||||
expect(page).to have_button('Stop')
|
||||
|
|
@ -380,8 +353,4 @@ RSpec.describe 'Environment' do
|
|||
def visit_environment(environment)
|
||||
visit project_environment_path(environment.project, environment)
|
||||
end
|
||||
|
||||
def have_terminal_button
|
||||
have_link(nil, href: terminal_project_environment_path(project, environment))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,238 @@
|
|||
import { GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import DeleteEnvironmentModal from '~/environments/components/delete_environment_modal.vue';
|
||||
import EnvironmentsDetailHeader from '~/environments/components/environments_detail_header.vue';
|
||||
import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { createEnvironment } from './mock_data';
|
||||
|
||||
describe('Environments detail header component', () => {
|
||||
const cancelAutoStopPath = '/my-environment/cancel/path';
|
||||
const terminalPath = '/my-environment/terminal/path';
|
||||
const metricsPath = '/my-environment/metrics/path';
|
||||
const updatePath = '/my-environment/edit/path';
|
||||
|
||||
let wrapper;
|
||||
|
||||
const findHeader = () => wrapper.findByRole('heading');
|
||||
const findAutoStopsAt = () => wrapper.findByTestId('auto-stops-at');
|
||||
const findCancelAutoStopAtButton = () => wrapper.findByTestId('cancel-auto-stop-button');
|
||||
const findCancelAutoStopAtForm = () => wrapper.findByTestId('cancel-auto-stop-form');
|
||||
const findTerminalButton = () => wrapper.findByTestId('terminal-button');
|
||||
const findExternalUrlButton = () => wrapper.findByTestId('external-url-button');
|
||||
const findMetricsButton = () => wrapper.findByTestId('metrics-button');
|
||||
const findEditButton = () => wrapper.findByTestId('edit-button');
|
||||
const findStopButton = () => wrapper.findByTestId('stop-button');
|
||||
const findDestroyButton = () => wrapper.findByTestId('destroy-button');
|
||||
const findStopEnvironmentModal = () => wrapper.findComponent(StopEnvironmentModal);
|
||||
const findDeleteEnvironmentModal = () => wrapper.findComponent(DeleteEnvironmentModal);
|
||||
|
||||
const buttons = [
|
||||
['Cancel Auto Stop At', findCancelAutoStopAtButton],
|
||||
['Terminal', findTerminalButton],
|
||||
['External Url', findExternalUrlButton],
|
||||
['Metrics', findMetricsButton],
|
||||
['Edit', findEditButton],
|
||||
['Stop', findStopButton],
|
||||
['Destroy', findDestroyButton],
|
||||
];
|
||||
|
||||
const createWrapper = ({ props }) => {
|
||||
wrapper = shallowMountExtended(EnvironmentsDetailHeader, {
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
TimeAgo,
|
||||
},
|
||||
propsData: {
|
||||
canReadEnvironment: false,
|
||||
canAdminEnvironment: false,
|
||||
canUpdateEnvironment: false,
|
||||
canStopEnvironment: false,
|
||||
canDestroyEnvironment: false,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('default state with minimal access', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({ props: { environment: createEnvironment() } });
|
||||
});
|
||||
|
||||
it('displays the environment name', () => {
|
||||
expect(findHeader().text()).toBe('My environment');
|
||||
});
|
||||
|
||||
it('does not display an auto stops at text', () => {
|
||||
expect(findAutoStopsAt().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it.each(buttons)('does not display button: %s', (_, findSelector) => {
|
||||
expect(findSelector().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not display stop environment modal', () => {
|
||||
expect(findStopEnvironmentModal().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not display delete environment modal', () => {
|
||||
expect(findDeleteEnvironmentModal().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when auto stops at is enabled and environment is available', () => {
|
||||
beforeEach(() => {
|
||||
const now = new Date();
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(now.getDate() + 1);
|
||||
createWrapper({
|
||||
props: {
|
||||
environment: createEnvironment({ autoStopAt: tomorrow.toISOString() }),
|
||||
cancelAutoStopPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('displays a text that describes when the environment is going to be stopped', () => {
|
||||
expect(findAutoStopsAt().text()).toBe('Auto stops in 1 day');
|
||||
});
|
||||
|
||||
it('displays a cancel auto stops at button with a form to make a post request', () => {
|
||||
const button = findCancelAutoStopAtButton();
|
||||
const form = findCancelAutoStopAtForm();
|
||||
expect(form.attributes('action')).toBe(cancelAutoStopPath);
|
||||
expect(form.attributes('method')).toBe('POST');
|
||||
expect(button.props('icon')).toBe('thumbtack');
|
||||
expect(button.attributes('type')).toBe('submit');
|
||||
});
|
||||
|
||||
it('includes a csrf token', () => {
|
||||
const input = findCancelAutoStopAtForm().find('input');
|
||||
expect(input.attributes('name')).toBe('authenticity_token');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when auto stops at is enabled and environment is unavailable (already stopped)', () => {
|
||||
beforeEach(() => {
|
||||
const now = new Date();
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(now.getDate() + 1);
|
||||
createWrapper({
|
||||
props: {
|
||||
environment: createEnvironment({
|
||||
autoStopAt: tomorrow.toISOString(),
|
||||
isAvailable: false,
|
||||
}),
|
||||
cancelAutoStopPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not display a text that describes when the environment is going to be stopped', () => {
|
||||
expect(findAutoStopsAt().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('displays a cancel auto stops at button with correct path', () => {
|
||||
expect(findCancelAutoStopAtButton().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when has a terminal', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
props: {
|
||||
environment: createEnvironment({ hasTerminals: true }),
|
||||
canAdminEnvironment: true,
|
||||
terminalPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the terminal button with correct path', () => {
|
||||
expect(findTerminalButton().attributes('href')).toBe(terminalPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when has an external url enabled', () => {
|
||||
const externalUrl = 'https://example.com/my-environment/external/url';
|
||||
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
props: {
|
||||
environment: createEnvironment({ hasTerminals: true, externalUrl }),
|
||||
canReadEnvironment: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the external url button with correct path', () => {
|
||||
expect(findExternalUrlButton().attributes('href')).toBe(externalUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when metrics are enabled', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
props: {
|
||||
environment: createEnvironment(),
|
||||
canReadEnvironment: true,
|
||||
metricsPath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the metrics button with correct path', () => {
|
||||
expect(findMetricsButton().attributes('href')).toBe(metricsPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when has all admin rights', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
props: {
|
||||
environment: createEnvironment(),
|
||||
canReadEnvironment: true,
|
||||
canAdminEnvironment: true,
|
||||
canStopEnvironment: true,
|
||||
canUpdateEnvironment: true,
|
||||
updatePath,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the edit button with correct path', () => {
|
||||
expect(findEditButton().attributes('href')).toBe(updatePath);
|
||||
});
|
||||
|
||||
it('displays the stop button with correct icon', () => {
|
||||
expect(findStopButton().attributes('icon')).toBe('stop');
|
||||
});
|
||||
|
||||
it('displays stop environment modal', () => {
|
||||
expect(findStopEnvironmentModal().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the environment is unavailable and user has destroy permissions', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
props: {
|
||||
environment: createEnvironment({ isAvailable: false }),
|
||||
canDestroyEnvironment: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('displays a delete button', () => {
|
||||
expect(findDestroyButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays delete environment modal', () => {
|
||||
expect(findDeleteEnvironmentModal().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -301,4 +301,22 @@ const tableData = {
|
|||
},
|
||||
};
|
||||
|
||||
export { environment, environmentsList, folder, serverData, tableData, deployBoardMockData };
|
||||
const createEnvironment = (data = {}) => ({
|
||||
id: 1,
|
||||
name: 'My environment',
|
||||
externalUrl: 'my external url',
|
||||
isAvailable: true,
|
||||
hasTerminals: false,
|
||||
autoStopAt: null,
|
||||
...data,
|
||||
});
|
||||
|
||||
export {
|
||||
environment,
|
||||
environmentsList,
|
||||
folder,
|
||||
serverData,
|
||||
tableData,
|
||||
deployBoardMockData,
|
||||
createEnvironment,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import CleanupStatus from '~/registry/explorer/components/list_page/cleanup_status.vue';
|
||||
import {
|
||||
ASYNC_DELETE_IMAGE_ERROR_MESSAGE,
|
||||
CLEANUP_TIMED_OUT_ERROR_MESSAGE,
|
||||
CLEANUP_STATUS_SCHEDULED,
|
||||
CLEANUP_STATUS_ONGOING,
|
||||
CLEANUP_STATUS_UNFINISHED,
|
||||
|
|
@ -81,7 +81,7 @@ describe('cleanup_status', () => {
|
|||
|
||||
const tooltip = getBinding(findExtraInfoIcon().element, 'gl-tooltip');
|
||||
|
||||
expect(tooltip.value.title).toBe(ASYNC_DELETE_IMAGE_ERROR_MESSAGE);
|
||||
expect(tooltip.value.title).toBe(CLEANUP_TIMED_OUT_ERROR_MESSAGE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import createFlash from '~/flash';
|
||||
import { updateHistory } from '~/lib/utils/url_utility';
|
||||
|
||||
import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue';
|
||||
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
|
||||
import RunnerList from '~/runner/components/runner_list.vue';
|
||||
import RunnerManualSetupHelp from '~/runner/components/runner_manual_setup_help.vue';
|
||||
|
|
@ -22,7 +23,6 @@ import {
|
|||
RUNNER_PAGE_SIZE,
|
||||
} from '~/runner/constants';
|
||||
import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql';
|
||||
import RunnerListApp from '~/runner/runner_list/runner_list_app.vue';
|
||||
import { captureException } from '~/runner/sentry_utils';
|
||||
|
||||
import { runnersData, runnersDataPaginated } from '../mock_data';
|
||||
|
|
@ -40,7 +40,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
|
|||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
describe('RunnerListApp', () => {
|
||||
describe('AdminRunnersApp', () => {
|
||||
let wrapper;
|
||||
let mockRunnersQuery;
|
||||
let originalLocation;
|
||||
|
|
@ -54,7 +54,7 @@ describe('RunnerListApp', () => {
|
|||
const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
|
||||
const handlers = [[getRunnersQuery, mockRunnersQuery]];
|
||||
|
||||
wrapper = mountFn(RunnerListApp, {
|
||||
wrapper = mountFn(AdminRunnersApp, {
|
||||
localVue,
|
||||
apolloProvider: createMockApollo(handlers),
|
||||
propsData: {
|
||||
|
|
@ -197,7 +197,7 @@ describe('RunnerListApp', () => {
|
|||
it('error is reported to sentry', async () => {
|
||||
expect(captureException).toHaveBeenCalledWith({
|
||||
error: new Error('Network error: Error!'),
|
||||
component: 'RunnerListApp',
|
||||
component: 'AdminRunnersApp',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -3,7 +3,7 @@ import {
|
|||
fromUrlQueryToSearch,
|
||||
fromSearchToUrl,
|
||||
fromSearchToVariables,
|
||||
} from '~/runner/runner_list/runner_search_utils';
|
||||
} from '~/runner/runner_search_utils';
|
||||
|
||||
describe('search_params.js', () => {
|
||||
const examples = [
|
||||
|
|
@ -22,4 +22,41 @@ RSpec.describe EnvironmentHelper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#environments_detail_data_json' do
|
||||
subject { helper.environments_detail_data_json(user, project, environment) }
|
||||
|
||||
let_it_be(:auto_stop_at) { Time.now.utc }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, :repository) }
|
||||
let_it_be(:environment) { create(:environment, project: project, auto_stop_at: auto_stop_at) }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(user)
|
||||
allow(helper).to receive(:can?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns the correct data' do
|
||||
expect(subject).to eq({
|
||||
name: environment.name,
|
||||
id: environment.id,
|
||||
external_url: environment.external_url,
|
||||
can_update_environment: true,
|
||||
can_destroy_environment: true,
|
||||
can_read_environment: true,
|
||||
can_stop_environment: true,
|
||||
can_admin_environment: true,
|
||||
environment_metrics_path: environment_metrics_path(environment),
|
||||
environments_fetch_path: project_environments_path(project, format: :json),
|
||||
environment_edit_path: edit_project_environment_path(project, environment),
|
||||
environment_stop_path: stop_project_environment_path(project, environment),
|
||||
environment_delete_path: environment_delete_path(environment),
|
||||
environment_cancel_auto_stop_path: cancel_auto_stop_project_environment_path(project, environment),
|
||||
environment_terminal_path: terminal_project_environment_path(project, environment),
|
||||
has_terminals: false,
|
||||
is_environment_available: true,
|
||||
auto_stop_at: auto_stop_at
|
||||
}.to_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue