Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-07-21 18:09:27 +00:00
parent a8f5aaa708
commit bd7e8cd64b
42 changed files with 832 additions and 315 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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"]

View File

@ -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`

View File

@ -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>

View File

@ -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({

View File

@ -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,
},
});
},

View File

@ -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();
}

View File

@ -1,3 +1,3 @@
import initShowEnvironment from '~/environments/mount_show';
import { initHeader } from '~/environments/mount_show';
initShowEnvironment();
initHeader();

View File

@ -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"

View File

@ -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,

View File

@ -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,

View File

@ -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);

View File

@ -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
//

View File

@ -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

View File

@ -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
'&nbsp;'.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

View File

@ -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

View File

@ -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")

View File

@ -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")

View File

@ -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')

View File

@ -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')

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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/):

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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',

View File

@ -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'

View File

@ -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

View File

@ -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);
});
});
});

View File

@ -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,
};

View File

@ -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);
});
});
});

View File

@ -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',
});
});

View File

@ -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 = [

View File

@ -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