diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-21 18:09:27 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-21 18:09:27 +0000 |
commit | bd7e8cd64b1eb9b2d5aa2f83e547d2a5b519b26c (patch) | |
tree | 292f1690eec2707d091ede0188706ec92da16427 | |
parent | a8f5aaa7081cc2d400fbac1106d9e94d02d70ab4 (diff) | |
download | gitlab-ce-bd7e8cd64b1eb9b2d5aa2f83e547d2a5b519b26c.tar.gz |
Add latest changes from gitlab-org/gitlab@master
42 files changed, 832 insertions, 315 deletions
diff --git a/.gitlab/ci/build-images.gitlab-ci.yml b/.gitlab/ci/build-images.gitlab-ci.yml index 853f92ed98d..0169f017063 100644 --- a/.gitlab/ci/build-images.gitlab-ci.yml +++ b/.gitlab/ci/build-images.gitlab-ci.yml @@ -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: diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index 8bbd7dbf075..c3e6de76894 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -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: diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 38d59af5aed..2b8c0dce162 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -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"] diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 48a8b9348ab..c74865b9fc5 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -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` diff --git a/app/assets/javascripts/environments/components/environments_detail_header.vue b/app/assets/javascripts/environments/components/environments_detail_header.vue new file mode 100644 index 00000000000..467c89fd8b8 --- /dev/null +++ b/app/assets/javascripts/environments/components/environments_detail_header.vue @@ -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> diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 6f701f87261..85cff73cc3e 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -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({ diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js index d0b68b0c14f..f1c2dfec94b 100644 --- a/app/assets/javascripts/environments/mount_show.js +++ b/app/assets/javascripts/environments/mount_show.js @@ -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, }, }); }, diff --git a/app/assets/javascripts/pages/admin/runners/index/index.js b/app/assets/javascripts/pages/admin/runners/index/index.js index d5563470394..8e7861c300a 100644 --- a/app/assets/javascripts/pages/admin/runners/index/index.js +++ b/app/assets/javascripts/pages/admin/runners/index/index.js @@ -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(); } diff --git a/app/assets/javascripts/pages/projects/environments/show/index.js b/app/assets/javascripts/pages/projects/environments/show/index.js index a4960037eaa..cd3a0d62bb6 100644 --- a/app/assets/javascripts/pages/projects/environments/show/index.js +++ b/app/assets/javascripts/pages/projects/environments/show/index.js @@ -1,3 +1,3 @@ -import initShowEnvironment from '~/environments/mount_show'; +import { initHeader } from '~/environments/mount_show'; -initShowEnvironment(); +initHeader(); diff --git a/app/assets/javascripts/registry/explorer/components/list_page/cleanup_status.vue b/app/assets/javascripts/registry/explorer/components/list_page/cleanup_status.vue index 8d9e221af4c..1f52e319ad0 100644 --- a/app/assets/javascripts/registry/explorer/components/list_page/cleanup_status.vue +++ b/app/assets/javascripts/registry/explorer/components/list_page/cleanup_status.vue @@ -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" diff --git a/app/assets/javascripts/runner/runner_list/runner_list_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue index 8d39243d609..23ecee449a4 100644 --- a/app/assets/javascripts/runner/runner_list/runner_list_app.vue +++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue @@ -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, diff --git a/app/assets/javascripts/runner/runner_list/index.js b/app/assets/javascripts/runner/admin_runners/index.js index 16616f00d1e..1eec1019b73 100644 --- a/app/assets/javascripts/runner/runner_list/index.js +++ b/app/assets/javascripts/runner/admin_runners/index.js @@ -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, diff --git a/app/assets/javascripts/runner/runner_list/runner_search_utils.js b/app/assets/javascripts/runner/runner_search_utils.js index 9a0dc9c3a32..65f75eb11ac 100644 --- a/app/assets/javascripts/runner/runner_list/runner_search_utils.js +++ b/app/assets/javascripts/runner/runner_search_utils.js @@ -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); diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 5ad7ceecb2b..465b1a80fa4 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -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 // diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 491d2731e91..3f23f73eed7 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -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 diff --git a/app/presenters/gitlab/blame_presenter.rb b/app/presenters/gitlab/blame_presenter.rb index 26c78384144..1f2445b04a1 100644 --- a/app/presenters/gitlab/blame_presenter.rb +++ b/app/presenters/gitlab/blame_presenter.rb @@ -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 diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index f9c52d9316b..5dce44a787c 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -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 diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml deleted file mode 100644 index b9208969fb3..00000000000 --- a/app/views/projects/environments/_external_url.html.haml +++ /dev/null @@ -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") diff --git a/app/views/projects/environments/_metrics_button.html.haml b/app/views/projects/environments/_metrics_button.html.haml deleted file mode 100644 index 65abaf44082..00000000000 --- a/app/views/projects/environments/_metrics_button.html.haml +++ /dev/null @@ -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") diff --git a/app/views/projects/environments/_pin_button.html.haml b/app/views/projects/environments/_pin_button.html.haml deleted file mode 100644 index ec3e7e20365..00000000000 --- a/app/views/projects/environments/_pin_button.html.haml +++ /dev/null @@ -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') diff --git a/app/views/projects/environments/_terminal_button.html.haml b/app/views/projects/environments/_terminal_button.html.haml deleted file mode 100644 index ab3363bbb07..00000000000 --- a/app/views/projects/environments/_terminal_button.html.haml +++ /dev/null @@ -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') diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index b3e4b7a4998..b123b81b89c 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -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? diff --git a/config/feature_flags/development/runner_registration_control.yml b/config/feature_flags/development/runner_registration_control.yml index 21edb90474d..56c01bf36e5 100644 --- a/config/feature_flags/development/runner_registration_control.yml +++ b/config/feature_flags/development/runner_registration_control.yml @@ -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 diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md index 3b82b0e4bb8..6de60162d46 100644 --- a/doc/administration/monitoring/performance/grafana_configuration.md +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -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 diff --git a/doc/subscriptions/quarterly_reconciliation.md b/doc/subscriptions/quarterly_reconciliation.md new file mode 100644 index 00000000000..af9fec1ddc4 --- /dev/null +++ b/doc/subscriptions/quarterly_reconciliation.md @@ -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. diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md index ecaeec35dd1..1ddecc77d2f 100644 --- a/doc/subscriptions/self_managed/index.md +++ b/doc/subscriptions/self_managed/index.md @@ -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/): diff --git a/doc/user/admin_area/analytics/dev_ops_report.md b/doc/user/admin_area/analytics/dev_ops_report.md index 89d8a5ea054..592b93796a6 100644 --- a/doc/user/admin_area/analytics/dev_ops_report.md +++ b/doc/user/admin_area/analytics/dev_ops_report.md @@ -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 diff --git a/doc/user/group/devops_adoption/index.md b/doc/user/group/devops_adoption/index.md index 4332f261481..948fca00593 100644 --- a/doc/user/group/devops_adoption/index.md +++ b/doc/user/group/devops_adoption/index.md @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 669900d1bf6..28f10728179 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" @@ -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 diff --git a/qa/qa/page/project/deployments/environments/show.rb b/qa/qa/page/project/deployments/environments/show.rb deleted file mode 100644 index 48e4850d3be..00000000000 --- a/qa/qa/page/project/deployments/environments/show.rb +++ /dev/null @@ -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 diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb index c1625f1e679..adacedb36ab 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb @@ -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 diff --git a/scripts/static-analysis b/scripts/static-analysis index fc917f1b975..a1859254459 100755 --- a/scripts/static-analysis +++ b/scripts/static-analysis @@ -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, diff --git a/scripts/trigger-build b/scripts/trigger-build index cb235677b5d..c65fb5b5f76 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -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', diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb index e8f197b67c2..ee5afb22109 100644 --- a/spec/features/projects/environments/environment_metrics_spec.rb +++ b/spec/features/projects/environments/environment_metrics_spec.rb @@ -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' diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index fea054de64e..5320f68b525 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -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 diff --git a/spec/frontend/environments/environments_detail_header_spec.js b/spec/frontend/environments/environments_detail_header_spec.js new file mode 100644 index 00000000000..6334060c736 --- /dev/null +++ b/spec/frontend/environments/environments_detail_header_spec.js @@ -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); + }); + }); +}); diff --git a/spec/frontend/environments/mock_data.js b/spec/frontend/environments/mock_data.js index 9ba71b78c2f..b04d0551b50 100644 --- a/spec/frontend/environments/mock_data.js +++ b/spec/frontend/environments/mock_data.js @@ -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, +}; diff --git a/spec/frontend/registry/explorer/components/list_page/cleanup_status_spec.js b/spec/frontend/registry/explorer/components/list_page/cleanup_status_spec.js index c89bb874a7f..8f2c049a357 100644 --- a/spec/frontend/registry/explorer/components/list_page/cleanup_status_spec.js +++ b/spec/frontend/registry/explorer/components/list_page/cleanup_status_spec.js @@ -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); }); }); }); diff --git a/spec/frontend/runner/runner_list/runner_list_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js index 54b7d1f1bdb..2c28f10ca8e 100644 --- a/spec/frontend/runner/runner_list/runner_list_app_spec.js +++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js @@ -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', }); }); diff --git a/spec/frontend/runner/runner_list/runner_search_utils_spec.js b/spec/frontend/runner/runner_search_utils_spec.js index e7969676549..3a0c3abe7bd 100644 --- a/spec/frontend/runner/runner_list/runner_search_utils_spec.js +++ b/spec/frontend/runner/runner_search_utils_spec.js @@ -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 = [ diff --git a/spec/helpers/environment_helper_spec.rb b/spec/helpers/environment_helper_spec.rb index 8c542ca01f4..0eecae32cc1 100644 --- a/spec/helpers/environment_helper_spec.rb +++ b/spec/helpers/environment_helper_spec.rb @@ -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 |