diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-31 15:08:32 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-31 15:08:32 +0000 |
commit | 85ea3dd4f4855e99d9a69c8e609095199597195a (patch) | |
tree | 46e74d30d0ef5cced04005b4a76c66ec18f6a749 | |
parent | 5820d448c17f93606afb52d878c00d84681764e0 (diff) | |
download | gitlab-ce-85ea3dd4f4855e99d9a69c8e609095199597195a.tar.gz |
Add latest changes from gitlab-org/gitlab@master
72 files changed, 869 insertions, 399 deletions
diff --git a/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue b/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue index 4156717908d..5e5d799d627 100644 --- a/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue +++ b/app/assets/javascripts/ci_settings_pipeline_triggers/components/triggers_list.vue @@ -1,10 +1,9 @@ <script> -import { GlTable, GlButton, GlBadge, GlTooltipDirective } from '@gitlab/ui'; +import { GlTable, GlButton, GlBadge, GlTooltipDirective, GlAvatarLink, GlAvatar } from '@gitlab/ui'; import { s__ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; -import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; export default { i18n: { @@ -21,7 +20,8 @@ export default { GlBadge, ClipboardButton, TooltipOnTruncate, - UserAvatarLink, + GlAvatarLink, + GlAvatar, TimeAgoTooltip, }, directives: { @@ -102,13 +102,14 @@ export default { </template> <template #cell(owner)="{ item }"> <span class="trigger-owner sr-only">{{ item.owner.name }}</span> - <user-avatar-link + <gl-avatar-link v-if="item.owner" - :link-href="item.owner.path" - :img-src="item.owner.avatarUrl" - :tooltip-text="item.owner.name" - :img-alt="item.owner.name" - /> + v-gl-tooltip + :href="item.owner.path" + :title="item.owner.name" + > + <gl-avatar :size="24" :src="item.owner.avatarUrl" /> + </gl-avatar-link> </template> <template #cell(lastUsed)="{ item }"> <time-ago-tooltip v-if="item.lastUsed" :time="item.lastUsed" /> diff --git a/app/assets/javascripts/ide/components/jobs/detail/description.vue b/app/assets/javascripts/ide/components/jobs/detail/description.vue index 9eaeabad5ef..8fd1973267c 100644 --- a/app/assets/javascripts/ide/components/jobs/detail/description.vue +++ b/app/assets/javascripts/ide/components/jobs/detail/description.vue @@ -1,6 +1,6 @@ <script> import { GlIcon } from '@gitlab/ui'; -import CiIcon from '../../../../vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; export default { components: { diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue index 996bf307b8c..d75f686051d 100644 --- a/app/assets/javascripts/issues/show/components/description.vue +++ b/app/assets/javascripts/issues/show/components/description.vue @@ -4,6 +4,8 @@ import $ from 'jquery'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { TYPE_WORK_ITEM } from '~/graphql_shared/constants'; import createFlash from '~/flash'; +import { isPositiveInteger } from '~/lib/utils/number_utils'; +import { getParameterByName, setUrlParams, updateHistory } from '~/lib/utils/url_utility'; import { __, s__, sprintf } from '~/locale'; import TaskList from '~/task_list'; import Tracking from '~/tracking'; @@ -65,13 +67,17 @@ export default { }, }, data() { + const workItemId = getParameterByName('work_item_id'); + return { preAnimation: false, pulseAnimation: false, initialUpdate: true, taskButtons: [], activeTask: {}, - workItemId: null, + workItemId: isPositiveInteger(workItemId) + ? convertToGraphQLId(TYPE_WORK_ITEM, workItemId) + : undefined, }; }, computed: { @@ -184,6 +190,7 @@ export default { taskLink.addEventListener('click', (e) => { e.preventDefault(); this.workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue); + this.updateWorkItemIdUrlQuery(issue); this.track('viewed_work_item_from_modal', { category: 'workItems:show', label: 'work_item_view', @@ -232,12 +239,19 @@ export default { this.$refs.modal.hide(); }, closeWorkItemDetailModal() { - this.workItemId = null; + this.workItemId = undefined; + this.updateWorkItemIdUrlQuery(undefined); }, handleCreateTask(description) { this.$emit('updateDescription', description); this.closeCreateTaskModal(); }, + updateWorkItemIdUrlQuery(workItemId) { + updateHistory({ + url: setUrlParams({ work_item_id: workItemId }), + replace: true, + }); + }, }, safeHtmlConfig: { ADD_TAGS: ['gl-emoji', 'copy-code'] }, }; @@ -281,7 +295,7 @@ export default { body-class="gl-p-0!" > <create-work-item - :is-modal="true" + is-modal :initial-title="activeTask.title" :issue-gid="issueGid" :lock-version="lockVersion" diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue index 01e3f84d00e..65b3fd6f8b3 100644 --- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue +++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue @@ -57,7 +57,7 @@ export default { :link-href="author.path" :img-alt="author.name" :img-src="author.avatar_url" - :img-size="26" + :img-size="24" :tooltip-text="author.name" tooltip-placement="bottom" /> diff --git a/app/assets/javascripts/pages/admin/groups/new/index.js b/app/assets/javascripts/pages/admin/groups/new/index.js index 1630cfb8253..710d2d72f4c 100644 --- a/app/assets/javascripts/pages/admin/groups/new/index.js +++ b/app/assets/javascripts/pages/admin/groups/new/index.js @@ -1,6 +1,6 @@ import initFilePickers from '~/file_pickers'; -import BindInOut from '../../../../behaviors/bind_in_out'; -import Group from '../../../../group'; +import BindInOut from '~/behaviors/bind_in_out'; +import Group from '~/group'; (() => { BindInOut.initAll(); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue index ee70ff858be..37e8a316ee4 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue @@ -2,8 +2,7 @@ import { GlButton } from '@gitlab/ui'; import Vue from 'vue'; import { getCookie, setCookie, parseBoolean } from '~/lib/utils/common_utils'; - -import Translate from '../../../../../vue_shared/translate'; +import Translate from '~/vue_shared/translate'; Vue.use(Translate); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js index 9c039a6be81..88ff05b20a0 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js @@ -3,9 +3,9 @@ import Vue from 'vue'; import { __ } from '~/locale'; import RefSelector from '~/ref/components/ref_selector.vue'; import { REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants'; -import setupNativeFormVariableList from '../../../../ci_variable_list/native_form_variable_list'; -import GlFieldErrors from '../../../../gl_field_errors'; -import Translate from '../../../../vue_shared/translate'; +import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list'; +import GlFieldErrors from '~/gl_field_errors'; +import Translate from '~/vue_shared/translate'; import intervalPatternInput from './components/interval_pattern_input.vue'; import TimezoneDropdown from './components/timezone_dropdown'; diff --git a/app/assets/javascripts/pages/projects/tags/new/index.js b/app/assets/javascripts/pages/projects/tags/new/index.js index b071e7a45fc..9ef1017f9f2 100644 --- a/app/assets/javascripts/pages/projects/tags/new/index.js +++ b/app/assets/javascripts/pages/projects/tags/new/index.js @@ -1,7 +1,7 @@ import $ from 'jquery'; -import GLForm from '../../../../gl_form'; -import RefSelectDropdown from '../../../../ref_select_dropdown'; -import ZenMode from '../../../../zen_mode'; +import GLForm from '~/gl_form'; +import RefSelectDropdown from '~/ref_select_dropdown'; +import ZenMode from '~/zen_mode'; new ZenMode(); // eslint-disable-line no-new new GLForm($('.tag-form')); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js index 4bb461aadad..cf7162f477d 100644 --- a/app/assets/javascripts/pages/projects/tree/show/index.js +++ b/app/assets/javascripts/pages/projects/tree/show/index.js @@ -1,8 +1,8 @@ import $ from 'jquery'; import initTree from 'ee_else_ce/repository'; import initBlob from '~/blob_edit/blob_bundle'; -import ShortcutsNavigation from '../../../../behaviors/shortcuts/shortcuts_navigation'; -import NewCommitForm from '../../../../new_commit_form'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; +import NewCommitForm from '~/new_commit_form'; new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new initBlob(); diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js index 2405bab7957..12e2cb2ee9f 100644 --- a/app/assets/javascripts/runner/admin_runners/index.js +++ b/app/assets/javascripts/runner/admin_runners/index.js @@ -26,7 +26,12 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { return null; } - const { runnerInstallHelpPage, registrationToken } = el.dataset; + const { + runnerInstallHelpPage, + registrationToken, + onlineContactTimeoutSecs, + staleTimeoutSecs, + } = el.dataset; const { cacheConfig, typeDefs, localMutations } = createLocalState(); @@ -40,6 +45,8 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { provide: { runnerInstallHelpPage, localMutations, + onlineContactTimeoutSecs, + staleTimeoutSecs, }, render(h) { return h(AdminRunnersApp, { diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue index 05cd1879d9d..dcfd4b84dd2 100644 --- a/app/assets/javascripts/runner/components/runner_list.vue +++ b/app/assets/javascripts/runner/components/runner_list.vue @@ -7,6 +7,7 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql'; import { formatJobCount, tableField } from '../utils'; import RunnerSummaryCell from './cells/runner_summary_cell.vue'; +import RunnerStatusPopover from './runner_status_popover.vue'; import RunnerStatusCell from './cells/runner_status_cell.vue'; import RunnerTags from './runner_tags.vue'; @@ -26,6 +27,7 @@ export default { GlSkeletonLoader, TooltipOnTruncate, TimeAgo, + RunnerStatusPopover, RunnerSummaryCell, RunnerTags, RunnerStatusCell, @@ -136,6 +138,11 @@ export default { /> </template> + <template #head(status)="{ label }"> + {{ label }} + <runner-status-popover /> + </template> + <template #cell(status)="{ item }"> <runner-status-cell :runner="item" /> </template> diff --git a/app/assets/javascripts/runner/components/runner_status_popover.vue b/app/assets/javascripts/runner/components/runner_status_popover.vue new file mode 100644 index 00000000000..5b22f7828a1 --- /dev/null +++ b/app/assets/javascripts/runner/components/runner_status_popover.vue @@ -0,0 +1,75 @@ +<script> +import { GlSprintf } from '@gitlab/ui'; +import { duration } from '~/lib/utils/datetime/timeago_utility'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; +import { + I18N_STATUS_POPOVER_TITLE, + I18N_STATUS_POPOVER_NEVER_CONTACTED, + I18N_STATUS_POPOVER_NEVER_CONTACTED_DESCRIPTION, + I18N_STATUS_POPOVER_ONLINE, + I18N_STATUS_POPOVER_ONLINE_DESCRIPTION, + I18N_STATUS_POPOVER_OFFLINE, + I18N_STATUS_POPOVER_OFFLINE_DESCRIPTION, + I18N_STATUS_POPOVER_STALE, + I18N_STATUS_POPOVER_STALE_DESCRIPTION, +} from '~/runner/constants'; + +export default { + name: 'RunnerStatusPopover', + components: { + GlSprintf, + HelpPopover, + }, + inject: ['onlineContactTimeoutSecs', 'staleTimeoutSecs'], + computed: { + onlineContactTimeoutDuration() { + return duration(this.onlineContactTimeoutSecs * 1000); + }, + staleTimeoutDuration() { + return duration(this.staleTimeoutSecs * 1000); + }, + }, + I18N_STATUS_POPOVER_TITLE, + I18N_STATUS_POPOVER_NEVER_CONTACTED, + I18N_STATUS_POPOVER_NEVER_CONTACTED_DESCRIPTION, + I18N_STATUS_POPOVER_ONLINE, + I18N_STATUS_POPOVER_ONLINE_DESCRIPTION, + I18N_STATUS_POPOVER_OFFLINE, + I18N_STATUS_POPOVER_OFFLINE_DESCRIPTION, + I18N_STATUS_POPOVER_STALE, + I18N_STATUS_POPOVER_STALE_DESCRIPTION, +}; +</script> + +<template> + <help-popover> + <template #title>{{ $options.I18N_STATUS_POPOVER_TITLE }}</template> + + <p class="gl-mb-0"> + <strong>{{ $options.I18N_STATUS_POPOVER_NEVER_CONTACTED }}</strong> + <gl-sprintf :message="$options.I18N_STATUS_POPOVER_NEVER_CONTACTED_DESCRIPTION"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + <p class="gl-mb-0"> + <strong>{{ $options.I18N_STATUS_POPOVER_ONLINE }}</strong> + <gl-sprintf :message="$options.I18N_STATUS_POPOVER_ONLINE_DESCRIPTION"> + <template #elapsedTime>{{ onlineContactTimeoutDuration }}</template> + </gl-sprintf> + </p> + <p class="gl-mb-0"> + <strong>{{ $options.I18N_STATUS_POPOVER_OFFLINE }}</strong> + <gl-sprintf :message="$options.I18N_STATUS_POPOVER_OFFLINE_DESCRIPTION"> + <template #elapsedTime>{{ onlineContactTimeoutDuration }}</template> + </gl-sprintf> + </p> + <p class="gl-mb-0"> + <strong>{{ $options.I18N_STATUS_POPOVER_STALE }}</strong> + <gl-sprintf :message="$options.I18N_STATUS_POPOVER_STALE_DESCRIPTION"> + <template #elapsedTime>{{ staleTimeoutDuration }}</template> + </gl-sprintf> + </p> + </help-popover> +</template> diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js index de0196e98a2..165968558c5 100644 --- a/app/assets/javascripts/runner/constants.js +++ b/app/assets/javascripts/runner/constants.js @@ -21,6 +21,26 @@ export const I18N_GROUP_RUNNER_DESCRIPTION = s__( ); export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one or more projects'); +// Status help popover +export const I18N_STATUS_POPOVER_TITLE = s__('Runners|Runner statuses'); + +export const I18N_STATUS_POPOVER_NEVER_CONTACTED = s__('Runners|Never contacted:'); +export const I18N_STATUS_POPOVER_NEVER_CONTACTED_DESCRIPTION = s__( + 'Runners|Runner has never contacted GitLab (when you register a runner, use %{codeStart}gitlab-runner run%{codeEnd} to bring it online)', +); +export const I18N_STATUS_POPOVER_ONLINE = s__('Runners|Online:'); +export const I18N_STATUS_POPOVER_ONLINE_DESCRIPTION = s__( + 'Runners|Runner has contacted GitLab within the last %{elapsedTime}', +); +export const I18N_STATUS_POPOVER_OFFLINE = s__('Runners|Offline:'); +export const I18N_STATUS_POPOVER_OFFLINE_DESCRIPTION = s__( + 'Runners|Runner has not contacted GitLab in more than %{elapsedTime}', +); +export const I18N_STATUS_POPOVER_STALE = s__('Runners|Stale:'); +export const I18N_STATUS_POPOVER_STALE_DESCRIPTION = s__( + 'Runners|Runner has not contacted GitLab in more than %{elapsedTime}', +); + // Status tooltips export const I18N_ONLINE_TIMEAGO_TOOLTIP = s__( 'Runners|Runner is online; last contact was %{timeAgo}', @@ -63,7 +83,7 @@ export const I18N_LOCKED_RUNNER_DESCRIPTION = s__( export const I18N_ASSIGNED_PROJECTS = s__('Runners|Assigned Projects (%{projectCount})'); export const I18N_NONE = __('None'); -export const I18N_NO_JOBS_FOUND = s__('Runner|This runner has not run any jobs.'); +export const I18N_NO_JOBS_FOUND = s__('Runners|This runner has not run any jobs.'); // Styles diff --git a/app/assets/javascripts/runner/group_runners/index.js b/app/assets/javascripts/runner/group_runners/index.js index 60b7a7ab541..0dade30f820 100644 --- a/app/assets/javascripts/runner/group_runners/index.js +++ b/app/assets/javascripts/runner/group_runners/index.js @@ -20,6 +20,8 @@ export const initGroupRunners = (selector = '#js-group-runners') => { groupId, groupFullPath, groupRunnersLimitedCount, + onlineContactTimeoutSecs, + staleTimeoutSecs, } = el.dataset; const apolloProvider = new VueApollo({ @@ -32,6 +34,8 @@ export const initGroupRunners = (selector = '#js-group-runners') => { provide: { runnerInstallHelpPage, groupId, + onlineContactTimeoutSecs: parseInt(onlineContactTimeoutSecs, 10), + staleTimeoutSecs: parseInt(staleTimeoutSecs, 10), }, render(h) { return h(GroupRunnersApp, { diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue index af85a2fda06..f28a2801bc0 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue @@ -1,6 +1,6 @@ <script> import { GlIcon } from '@gitlab/ui'; -import { numberToHumanSize } from '../../../../lib/utils/number_utils'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; export default { components: { diff --git a/app/assets/stylesheets/notify_enhanced.scss b/app/assets/stylesheets/notify_enhanced.scss index 5df5a8592bf..5aa0fd3cd00 100644 --- a/app/assets/stylesheets/notify_enhanced.scss +++ b/app/assets/stylesheets/notify_enhanced.scss @@ -26,11 +26,15 @@ a { text-decoration: none; } -.content { - .md { - padding: 1rem 0; - } +.gl-mb-5 { + @include gl-mb-5; +} +.gl-mt-5 { + @include gl-mt-5; +} + +.content { hr { border: 1px solid #e1e1e1; } diff --git a/app/components/diffs/base_component.rb b/app/components/diffs/base_component.rb new file mode 100644 index 00000000000..9e1347d1e84 --- /dev/null +++ b/app/components/diffs/base_component.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Diffs + class BaseComponent < ViewComponent::Base + # To make converting the partials to components easier, + # we delegate all missing methods to the helpers, + # where they probably are. + delegate_missing_to :helpers + end +end diff --git a/app/components/diffs/stats_component.html.haml b/app/components/diffs/stats_component.html.haml new file mode 100644 index 00000000000..c0e816639a7 --- /dev/null +++ b/app/components/diffs/stats_component.html.haml @@ -0,0 +1 @@ +.js-diff-stats-dropdown{ data: { changed: @changed, added: @added, deleted: @removed, files: diff_files_data } } diff --git a/app/components/diffs/stats_component.rb b/app/components/diffs/stats_component.rb new file mode 100644 index 00000000000..55589c7b015 --- /dev/null +++ b/app/components/diffs/stats_component.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Diffs + class StatsComponent < BaseComponent + attr_reader :diff_files + + def initialize(diff_files:) + @diff_files = diff_files + @changed ||= diff_files.size + @added ||= diff_files.sum(&:added_lines) + @removed ||= diff_files.sum(&:removed_lines) + end + + def diff_files_data + diffs_map = @diff_files.map do |f| + { + href: "##{helpers.hexdigest(f.file_path)}", + title: f.new_path, + name: f.file_path, + path: diff_file_path_text(f), + icon: diff_file_changed_icon(f), + iconColor: "#{diff_file_changed_icon_color(f)}", + added: f.added_lines, + removed: f.removed_lines + } + end + + Gitlab::Json.dump(diffs_map) + end + + # Disabled undercoverage reports for this method + # as it returns a false positive on the last line, + # which is covered in the tests + # + # Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/357381 + # + # :nocov: + def diff_file_path_text(diff_file, max: 60) + path = diff_file.new_path + + return path unless path.size > max && max > 3 + + "...#{path[-(max - 3)..]}" + end + # :nocov: + + private + + def diff_file_changed_icon(diff_file) + if diff_file.deleted_file? + "file-deletion" + elsif diff_file.new_file? + "file-addition" + else + "file-modified" + end + end + + def diff_file_changed_icon_color(diff_file) + if diff_file.deleted_file? + "danger" + elsif diff_file.new_file? + "success" + end + end + end +end diff --git a/app/components/pajamas/alert_component.html.haml b/app/components/pajamas/alert_component.html.haml new file mode 100644 index 00000000000..a1d3c700e57 --- /dev/null +++ b/app/components/pajamas/alert_component.html.haml @@ -0,0 +1,13 @@ +.gl-alert{ role: 'alert', class: ["gl-alert-#{@variant}", @alert_class], data: @alert_data } + = sprite_icon(icon, css_class: icon_classes) + - if @dismissible + %button.btn.gl-dismiss-btn.btn-default.btn-sm.gl-button.btn-default-tertiary.btn-icon.js-close{ type: 'button', + aria: { label: _('Dismiss') }, + class: @close_button_class, + data: @close_button_data } + = sprite_icon('close') + .gl-alert-content{ role: 'alert' } + - if @title + %h4.gl-alert-title + = @title + = content diff --git a/app/components/pajamas/alert_component.rb b/app/components/pajamas/alert_component.rb new file mode 100644 index 00000000000..4bb6c41661b --- /dev/null +++ b/app/components/pajamas/alert_component.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +# Renders a GlAlert root element +module Pajamas + class AlertComponent < Pajamas::Component + # @param [String] title + # @param [Symbol] variant + # @param [Boolean] dismissible + # @param [String] alert_class + # @param [Hash] alert_data + # @param [String] close_button_class + # @param [Hash] close_button_data + def initialize( + title: nil, variant: :info, dismissible: true, + alert_class: nil, alert_data: {}, close_button_class: nil, close_button_data: {}) + @title = title + @variant = variant + @dismissible = dismissible + @alert_class = alert_class + @alert_data = alert_data + @close_button_class = close_button_class + @close_button_data = close_button_data + end + + private + + delegate :sprite_icon, to: :helpers + + ICONS = { + info: 'information-o', + warning: 'warning', + success: 'check-circle', + danger: 'error', + tip: 'bulb' + }.freeze + + def icon + ICONS[@variant] + end + + def icon_classes + "gl-alert-icon#{' gl-alert-icon-no-title' if @title.nil?}" + end + end +end diff --git a/app/controllers/projects/packages/infrastructure_registry_controller.rb b/app/controllers/projects/packages/infrastructure_registry_controller.rb index 2fe353b7acb..99d75afc63a 100644 --- a/app/controllers/projects/packages/infrastructure_registry_controller.rb +++ b/app/controllers/projects/packages/infrastructure_registry_controller.rb @@ -9,7 +9,6 @@ module Projects def show @package = project.packages.find(params[:id]) - @package_files = @package.installable_package_files.recent end end end diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb index 28e4056daac..0e8b6fa6d25 100644 --- a/app/helpers/ci/runners_helper.rb +++ b/app/helpers/ci/runners_helper.rb @@ -63,7 +63,9 @@ module Ci # Runner install help page is external, located at # https://gitlab.com/gitlab-org/gitlab-runner runner_install_help_page: 'https://docs.gitlab.com/runner/install/', - registration_token: Gitlab::CurrentSettings.runners_registration_token + registration_token: Gitlab::CurrentSettings.runners_registration_token, + online_contact_timeout_secs: ::Ci::Runner::ONLINE_CONTACT_TIMEOUT.to_i, + stale_timeout_secs: ::Ci::Runner::STALE_TIMEOUT.to_i } end @@ -83,7 +85,9 @@ module Ci registration_token: group.runners_token, group_id: group.id, group_full_path: group.full_path, - runner_install_help_page: 'https://docs.gitlab.com/runner/install/' + runner_install_help_page: 'https://docs.gitlab.com/runner/install/', + online_contact_timeout_secs: ::Ci::Runner::ONLINE_CONTACT_TIMEOUT.to_i, + stale_timeout_secs: ::Ci::Runner::STALE_TIMEOUT.to_i } end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 2aabf0febdf..522593dd487 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -195,24 +195,6 @@ module DiffHelper !diff_file.deleted_file? && @merge_request && @merge_request.source_project end - def diff_file_changed_icon(diff_file) - if diff_file.deleted_file? - "file-deletion" - elsif diff_file.new_file? - "file-addition" - else - "file-modified" - end - end - - def diff_file_changed_icon_color(diff_file) - if diff_file.deleted_file? - "danger" - elsif diff_file.new_file? - "success" - end - end - def render_overflow_warning?(diffs_collection) diffs_collection.overflow?.tap do |overflown| log_overflow_limits(diff_files: diffs_collection.raw_diff_files, collection_overflow: overflown) @@ -272,23 +254,6 @@ module DiffHelper toggle_whitespace_link(url, options) end - def diff_files_data(diff_files) - diffs_map = diff_files.map do |f| - { - href: "##{hexdigest(f.file_path)}", - title: f.new_path, - name: f.file_path, - path: diff_file_path_text(f), - icon: diff_file_changed_icon(f), - iconColor: "#{diff_file_changed_icon_color(f)}", - added: f.added_lines, - removed: f.removed_lines - } - end - - diffs_map.to_json - end - def hide_whitespace? params[:w] == '1' end @@ -302,14 +267,6 @@ module DiffHelper link_to "#{hide_whitespace? ? 'Show' : 'Hide'} whitespace changes", url, class: options[:class] end - def diff_file_path_text(diff_file, max: 60) - path = diff_file.new_path - - return path unless path.size > max && max > 3 - - "...#{path[-(max - 3)..]}" - end - def code_navigation_path(diffs) Gitlab::CodeNavigationPath.new(merge_request.project, merge_request.diff_head_sha) end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 1eb30e88f16..729170da242 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -524,6 +524,10 @@ module Issuable labels.order('title ASC').pluck(:title) end + def labels_hook_attrs + labels.map(&:hook_attrs) + end + # Convert this Issuable class name to a format usable by Ability definitions # # Examples: diff --git a/app/models/issue.rb b/app/models/issue.rb index 5c91656cbc0..7bb388ed4dc 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -386,10 +386,6 @@ class Issue < ApplicationRecord resource_parent.root_namespace&.issue_repositioning_disabled? end - def hook_attrs - Gitlab::HookData::IssueBuilder.new(self).build - end - # `from` argument can be a Namespace or Project. def to_reference(from = nil, full: false) reference = "#{self.class.reference_prefix}#{iid}" @@ -530,10 +526,6 @@ class Issue < ApplicationRecord ::MergeRequestsClosingIssues.count_for_issue(self.id, user) end - def labels_hook_attrs - labels.map(&:hook_attrs) - end - def previous_updated_at previous_changes['updated_at']&.first || updated_at end diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb index 0f04eb7d4af..42914b0d668 100644 --- a/app/models/project_import_state.rb +++ b/app/models/project_import_state.rb @@ -58,9 +58,7 @@ class ProjectImportState < ApplicationRecord end after_transition any => :failed do |state, _| - if Feature.enabled?(:remove_import_data_on_failure, state.project, default_enabled: :yaml) - state.project.remove_import_data - end + state.project.remove_import_data end after_transition started: :finished do |state, _| diff --git a/app/views/layouts/header/_registration_enabled_callout.html.haml b/app/views/layouts/header/_registration_enabled_callout.html.haml index d1d23c86c81..affee15c4d0 100644 --- a/app/views/layouts/header/_registration_enabled_callout.html.haml +++ b/app/views/layouts/header/_registration_enabled_callout.html.haml @@ -1,11 +1,11 @@ - return unless show_registration_enabled_user_callout? -= render 'shared/global_alert', - title: _('Anyone can register for an account.'), += render Pajamas::AlertComponent.new(title: _('Anyone can register for an account.'), variant: :warning, alert_class: 'js-registration-enabled-callout', - alert_data: { feature_id: Users::CalloutsHelper::REGISTRATION_ENABLED_CALLOUT, dismiss_endpoint: callouts_path }, - close_button_data: { testid: 'close-registration-enabled-callout' } do + alert_data: { feature_id: Users::CalloutsHelper::REGISTRATION_ENABLED_CALLOUT, + dismiss_endpoint: callouts_path }, + close_button_data: { testid: 'close-registration-enabled-callout' }) do .gl-alert-body = _('Only allow anyone to register for accounts on GitLab instances that you intend to be used by anyone. Allowing anyone to register makes GitLab instances more vulnerable.') .gl-alert-actions diff --git a/app/views/layouts/header/_storage_enforcement_banner.html.haml b/app/views/layouts/header/_storage_enforcement_banner.html.haml index 851fc57e44d..92c02d6ecfd 100644 --- a/app/views/layouts/header/_storage_enforcement_banner.html.haml +++ b/app/views/layouts/header/_storage_enforcement_banner.html.haml @@ -3,7 +3,12 @@ - banner_info = storage_enforcement_banner_info(namespace) - return unless banner_info.present? -= render 'shared/global_alert', variant: :warning, alert_class: 'js-storage-enforcement-banner', alert_data: { feature_id: banner_info[:callouts_feature_name], dismiss_endpoint: banner_info[:callouts_path], group_id: namespace.id, defer_links: "true" } do += render Pajamas::AlertComponent.new(variant: :warning, + alert_class: 'js-storage-enforcement-banner', + alert_data: { feature_id: banner_info[:callouts_feature_name], + dismiss_endpoint: banner_info[:callouts_path], + group_id: namespace.id, + defer_links: "true" }) do .gl-alert-body = banner_info[:text] = banner_info[:learn_more_link] diff --git a/app/views/notify/_note_email.html.haml b/app/views/notify/_note_email.html.haml index f03e524a0f3..d5398337320 100644 --- a/app/views/notify/_note_email.html.haml +++ b/app/views/notify/_note_email.html.haml @@ -27,7 +27,7 @@ = content_for :head do = stylesheet_link_tag 'mailers/highlighted_diff_email' - %table.code + %table.code.gl-mb-5 = render partial: "projects/diffs/email_line", collection: discussion.truncated_diff_lines(diff_limit: diff_limit), as: :line, diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index f67ac5f8fb2..1542d5bba85 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -2,18 +2,17 @@ = html_escape(_('%{user} created a merge request: %{mr_link}')) % { user: link_to(@merge_request.author_name, user_url(@merge_request.author)), mr_link: merge_request_reference_link(@merge_request) } -%p - .branch - = merge_path_description(@merge_request, 'to') - .author - Author: #{@merge_request.author_name} - .assignee - = assignees_label(@merge_request) - .reviewer - = reviewers_label(@merge_request) - .approvers - = render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter +.branch + = merge_path_description(@merge_request, 'to') +.author + Author: #{@merge_request.author_name} +.assignee + = assignees_label(@merge_request) +.reviewer + = reviewers_label(@merge_request) +.approvers + = render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter - if @merge_request.description - .md + .md.gl-mt-5 = markdown(@merge_request.description, pipeline: :email, author: @merge_request.author, current_user: @recipient, issuable_reference_expansion_enabled: true) diff --git a/app/views/notify/service_desk_new_note_email.html.haml b/app/views/notify/service_desk_new_note_email.html.haml index 0c16cf3315f..02f6b3914c9 100644 --- a/app/views/notify/service_desk_new_note_email.html.haml +++ b/app/views/notify/service_desk_new_note_email.html.haml @@ -1,5 +1,5 @@ - if Gitlab::CurrentSettings.email_author_in_body - %div + .gl-mb-5 = _("%{author_link} wrote:").html_safe % { author_link: link_to(@note.author_name, user_url(@note.author)) } .md = markdown(@note.note, pipeline: :email, author: @note.author, issuable_reference_expansion_enabled: true) diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index bb2682bb7c0..7e3f9c7664e 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -24,7 +24,7 @@ .btn-group.gl-ml-3 = inline_diff_btn = parallel_diff_btn - = render 'projects/diffs/stats', diff_files: diff_files + = render Diffs::StatsComponent.new(diff_files: diff_files) - if render_overflow_warning?(diffs) = render 'projects/diffs/warning', diff_files: diffs diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml deleted file mode 100644 index fe9658a440a..00000000000 --- a/app/views/projects/diffs/_stats.html.haml +++ /dev/null @@ -1 +0,0 @@ -.js-diff-stats-dropdown{ data: { changed: diff_files.size, added: diff_files.sum(&:added_lines), deleted: diff_files.sum(&:removed_lines), files: diff_files_data(diff_files) } } diff --git a/app/views/shared/issuable/_assignees.html.haml b/app/views/shared/issuable/_assignees.html.haml index e6d722cb08d..73f1e35f03f 100644 --- a/app/views/shared/issuable/_assignees.html.haml +++ b/app/views/shared/issuable/_assignees.html.haml @@ -7,7 +7,7 @@ = render 'shared/issuable/merge_request_assignees', issuable: issuable, count: render_count - else - issuable.assignees.take(render_count).each do |assignee| # rubocop: disable CodeReuse/ActiveRecord - = link_to_member(@project, assignee, name: false, title: s_("MrList|Assigned to %{name}, go to their profile.") % { name: assignee.name}) + = link_to_member(@project, assignee, name: false, title: s_("MrList|Assigned to %{name}") % { name: assignee.name}) - if more_assignees_count > 0 %span{ class: 'avatar-counter has-tooltip', data: { container: 'body', placement: 'bottom', 'line-type' => 'old', qa_selector: 'avatar_counter_content' }, title: _("+%{more_assignees_count} more assignees") % { more_assignees_count: more_assignees_count} } diff --git a/app/views/shared/issuable/_merge_request_assignees.html.haml b/app/views/shared/issuable/_merge_request_assignees.html.haml index 13dc6ae4abb..6c7a2496ec6 100644 --- a/app/views/shared/issuable/_merge_request_assignees.html.haml +++ b/app/views/shared/issuable/_merge_request_assignees.html.haml @@ -1,6 +1,6 @@ - issuable.merge_request_assignees.take(count).each do |merge_request_assignee| # rubocop: disable CodeReuse/ActiveRecord - assignee = merge_request_assignee.assignee - - assignee_tooltip = ( merge_request_assignee.attention_requested? ? s_("MrList|Attention requested from assignee %{name}, go to their profile.") : s_("MrList|Assigned to %{name}, go to their profile.") ) % { name: assignee.name} + - assignee_tooltip = ( merge_request_assignee.attention_requested? ? s_("MrList|Attention requested from assignee %{name}") : s_("MrList|Assigned to %{name}") ) % { name: assignee.name} = link_to_member(@project, assignee, name: false, title: assignee_tooltip, extra_class: "gl-flex-direction-row-reverse") do - if merge_request_assignee.attention_requested? diff --git a/app/views/shared/issuable/_merge_request_reviewers.html.haml b/app/views/shared/issuable/_merge_request_reviewers.html.haml index df5c69e309f..8dd74e12aff 100644 --- a/app/views/shared/issuable/_merge_request_reviewers.html.haml +++ b/app/views/shared/issuable/_merge_request_reviewers.html.haml @@ -1,6 +1,6 @@ - issuable.merge_request_reviewers.take(count).each do |merge_request_reviewer| # rubocop: disable CodeReuse/ActiveRecord - reviewer = merge_request_reviewer.reviewer - - reviewer_tooltip = ( merge_request_reviewer.attention_requested? ? s_("MrList|Attention requested from reviewer %{name}, go to their profile.") : s_("MrList|Review requested from %{name}, go to their profile.") ) % { name: reviewer.name} + - reviewer_tooltip = ( merge_request_reviewer.attention_requested? ? s_("MrList|Attention requested from reviewer %{name}") : s_("MrList|Review requested from %{name}") ) % { name: reviewer.name} = link_to_member(@project, reviewer, name: false, title: reviewer_tooltip, extra_class: "gl-flex-direction-row-reverse") do - if merge_request_reviewer.attention_requested? diff --git a/app/views/shared/issuable/_reviewers.html.haml b/app/views/shared/issuable/_reviewers.html.haml index 0bb0faa0bb8..4af2cb00859 100644 --- a/app/views/shared/issuable/_reviewers.html.haml +++ b/app/views/shared/issuable/_reviewers.html.haml @@ -7,7 +7,7 @@ = render 'shared/issuable/merge_request_reviewers', issuable: issuable, count: render_count - else - issuable.reviewers.take(render_count).each do |reviewer| # rubocop: disable CodeReuse/ActiveRecord - = link_to_member(@project, reviewer, name: false, title: s_("MrList|Review requested from %{name}, go to their profile.") % { name: reviewer.name}) + = link_to_member(@project, reviewer, name: false, title: s_("MrList|Review requested from %{name}") % { name: reviewer.name}) - if more_reviewers_count > 0 %span{ class: 'avatar-counter has-tooltip', data: { container: 'body', placement: 'bottom', 'line-type' => 'old' }, title: _("+%{more_reviewers_count} more reviewers") % { more_reviewers_count: more_reviewers_count} } diff --git a/config/feature_flags/development/remove_import_data_on_failure.yml b/config/feature_flags/development/vulnerability_report_page_size_selector.yml index 341e027f28b..b410ee27adb 100644 --- a/config/feature_flags/development/remove_import_data_on_failure.yml +++ b/config/feature_flags/development/vulnerability_report_page_size_selector.yml @@ -1,8 +1,8 @@ --- -name: remove_import_data_on_failure -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80074 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352156 -milestone: '14.8' +name: vulnerability_report_page_size_selector +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82438 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/356888 +milestone: '14.10' type: development -group: group::source code -default_enabled: true +group: group::threat insights +default_enabled: false diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb index 0d874f5bebc..6953de670a7 100644 --- a/config/initializers/7_prometheus_metrics.rb +++ b/config/initializers/7_prometheus_metrics.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -PUMA_EXTERNAL_METRICS_SERVER = Gitlab::Utils.to_boolean(ENV['PUMA_EXTERNAL_METRICS_SERVER']) -require Rails.root.join('metrics_server', 'metrics_server') if PUMA_EXTERNAL_METRICS_SERVER +require Rails.root.join('metrics_server', 'metrics_server') # Keep separate directories for separate processes def metrics_temp_dir @@ -20,11 +19,17 @@ def prometheus_metrics_dir ENV['prometheus_multiproc_dir'] || metrics_temp_dir end -def puma_metrics_server_process? +def puma_master? Prometheus::PidProvider.worker_id == 'puma_master' end -if puma_metrics_server_process? +# Whether a dedicated process should run that serves Rails application metrics, as opposed +# to using a Rails controller. +def puma_dedicated_metrics_server? + Settings.monitoring.web_exporter.enabled +end + +if puma_master? # The following is necessary to ensure stale Prometheus metrics don't accumulate over time. # It needs to be done as early as possible to ensure new metrics aren't being deleted. # @@ -77,11 +82,7 @@ Gitlab::Cluster::LifecycleEvents.on_master_start do if Gitlab::Runtime.puma? Gitlab::Metrics::Samplers::PumaSampler.instance.start - if PUMA_EXTERNAL_METRICS_SERVER && Settings.monitoring.web_exporter.enabled - MetricsServer.start_for_puma - else - Gitlab::Metrics::Exporter::WebExporter.instance.start - end + MetricsServer.start_for_puma if puma_dedicated_metrics_server? end Gitlab::Ci::Parsers.instrument! @@ -100,11 +101,7 @@ Gitlab::Cluster::LifecycleEvents.on_worker_start do if Gitlab::Runtime.puma? # Since we are observing a metrics server from the Puma primary, we would inherit # this supervision thread after forking into workers, so we need to explicitly stop it here. - if PUMA_EXTERNAL_METRICS_SERVER - ::MetricsServer::PumaProcessSupervisor.instance.stop - else - Gitlab::Metrics::Exporter::WebExporter.instance.stop - end + ::MetricsServer::PumaProcessSupervisor.instance.stop if puma_dedicated_metrics_server? Gitlab::Metrics::Samplers::ActionCableSampler.instance(logger: logger).start end @@ -119,15 +116,11 @@ rescue IOError => e Gitlab::Metrics.error_detected! end -if Gitlab::Runtime.puma? +if Gitlab::Runtime.puma? && puma_dedicated_metrics_server? Gitlab::Cluster::LifecycleEvents.on_before_graceful_shutdown do # We need to ensure that before we re-exec or shutdown server # we also stop the metrics server - if PUMA_EXTERNAL_METRICS_SERVER - ::MetricsServer::PumaProcessSupervisor.instance.shutdown - else - Gitlab::Metrics::Exporter::WebExporter.instance.stop - end + ::MetricsServer::PumaProcessSupervisor.instance.shutdown end Gitlab::Cluster::LifecycleEvents.on_before_master_restart do @@ -136,10 +129,6 @@ if Gitlab::Runtime.puma? # # We do it again, for being extra safe, # but it should not be needed - if PUMA_EXTERNAL_METRICS_SERVER - ::MetricsServer::PumaProcessSupervisor.instance.shutdown - else - Gitlab::Metrics::Exporter::WebExporter.instance.stop - end + ::MetricsServer::PumaProcessSupervisor.instance.shutdown end end diff --git a/doc/subscriptions/img/support-diagram.png b/doc/subscriptions/img/support-diagram.png Binary files differdeleted file mode 100644 index 1628a62f19a..00000000000 --- a/doc/subscriptions/img/support-diagram.png +++ /dev/null diff --git a/doc/subscriptions/img/support_diagram_c.png b/doc/subscriptions/img/support_diagram_c.png Binary files differnew file mode 100644 index 00000000000..a2fed80e912 --- /dev/null +++ b/doc/subscriptions/img/support_diagram_c.png diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md index fbeeceb9dbb..824fe0bacde 100644 --- a/doc/subscriptions/index.md +++ b/doc/subscriptions/index.md @@ -256,12 +256,11 @@ Send all questions and requests related to the GitLab for Startups program to `s ### Support for Community Programs -Because these Community Programs are free of cost, regular Priority Support is not included. However, it can be purchased at a 95% discount in some cases. -If interested, email the relevant community program team: `education@gitlab.com`, `opensource@gitlab.com`, or `startups@gitlab.com`. +Because these Community Programs are free of cost, regular Priority Support is not included. As a community member, you can follow this diagram to find support: -![Support diagram](img/support-diagram.png) +![Support diagram](img/support_diagram_c.png) ## Contact Support diff --git a/doc/user/group/index.md b/doc/user/group/index.md index a93fd9927b0..e748f49c0ea 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -278,8 +278,8 @@ To view the activity feed in Atom format, select the [Feature flag `invite_members_group_modal`](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) removed. Similar to how you [share a project with a group](../project/members/share_project_with_groups.md), -you can share a group with another group. Members get direct access -to the shared group. This includes members who inherited group membership from a parent group. +you can share a group with another group. To invite a group, you must be a member of it. Members get direct access +to the shared group. This includes members who inherited group membership from a parent group. To share a given group, for example, `Frontend` with another group, for example, `Engineering`: diff --git a/doc/user/project/integrations/webhook_events.md b/doc/user/project/integrations/webhook_events.md index 53c56a7c9af..2bf6b4bbe01 100644 --- a/doc/user/project/integrations/webhook_events.md +++ b/doc/user/project/integrations/webhook_events.md @@ -538,6 +538,32 @@ Payload example: "iid": 1, "description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.", "position": 0, + "labels": [ + { + "id": 25, + "title": "Afterpod", + "color": "#3e8068", + "project_id": null, + "created_at": "2019-06-05T14:32:20.211Z", + "updated_at": "2019-06-05T14:32:20.211Z", + "template": false, + "description": null, + "type": "GroupLabel", + "group_id": 4 + }, + { + "id": 86, + "title": "Element", + "color": "#231afe", + "project_id": 4, + "created_at": "2019-06-05T14:32:20.637Z", + "updated_at": "2019-06-05T14:32:20.637Z", + "template": false, + "description": null, + "type": "ProjectLabel", + "group_id": null + } + ], "source":{ "name":"Gitlab Test", "description":"Aut reprehenderit ut est.", diff --git a/lib/gitlab/data_builder/note.rb b/lib/gitlab/data_builder/note.rb index 73518d36d43..dec583f5a42 100644 --- a/lib/gitlab/data_builder/note.rb +++ b/lib/gitlab/data_builder/note.rb @@ -43,10 +43,9 @@ module Gitlab if note.for_commit? data[:commit] = build_data_for_commit(project, user, note) elsif note.for_issue? - data[:issue] = note.noteable.hook_attrs - data[:issue][:labels] = note.noteable.labels_hook_attrs + data[:issue] = Gitlab::HookData::IssueBuilder.new(note.noteable).build elsif note.for_merge_request? - data[:merge_request] = note.noteable.hook_attrs + data[:merge_request] = Gitlab::HookData::MergeRequestBuilder.new(note.noteable).build elsif note.for_snippet? data[:snippet] = note.noteable.hook_attrs end diff --git a/lib/gitlab/hook_data/issuable_builder.rb b/lib/gitlab/hook_data/issuable_builder.rb index 5c8aa5050ed..c4e27bf424f 100644 --- a/lib/gitlab/hook_data/issuable_builder.rb +++ b/lib/gitlab/hook_data/issuable_builder.rb @@ -13,7 +13,7 @@ module Gitlab event_type: event_type, user: user.hook_attrs, project: issuable.project.hook_attrs, - object_attributes: issuable.hook_attrs, + object_attributes: issuable_builder.new(issuable).build, labels: issuable.labels.map(&:hook_attrs), changes: final_changes(changes.slice(*safe_keys)), # DEPRECATED diff --git a/lib/gitlab/hook_data/merge_request_builder.rb b/lib/gitlab/hook_data/merge_request_builder.rb index aaca16d8d7c..06ddd65d075 100644 --- a/lib/gitlab/hook_data/merge_request_builder.rb +++ b/lib/gitlab/hook_data/merge_request_builder.rb @@ -60,6 +60,7 @@ module Gitlab human_time_estimate: merge_request.human_time_estimate, assignee_ids: merge_request.assignee_ids, assignee_id: merge_request.assignee_ids.first, # This key is deprecated + labels: merge_request.labels_hook_attrs, state: merge_request.state, # This key is deprecated blocking_discussions_resolved: merge_request.mergeable_discussions_state? } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c6af956f4d7..dd6dab41ca8 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5686,6 +5686,12 @@ msgstr "" msgid "BillingPlans|While GitLab is ending availability of the Bronze plan, you can still renew your Bronze subscription one additional time before %{eoa_bronze_plan_end_date}. We are also offering a limited time free upgrade to our Premium Plan (up to 25 users)! Learn more about the changes and offers in our %{announcement_link}." msgstr "" +msgid "BillingPlans|You don't have any groups. You'll need to %{create_group_link_start}create one%{create_group_link_end} and %{move_link_start}move this project to it%{move_link_end}." +msgstr "" + +msgid "BillingPlans|You'll have to %{move_link_start}move this project%{move_link_end} to one of your groups." +msgstr "" + msgid "BillingPlans|Your GitLab.com %{plan} trial will %{strong_open}expire after %{expiration_date}%{strong_close}. You can retain access to the %{plan} features by upgrading below." msgstr "" @@ -24566,16 +24572,16 @@ msgstr "" msgid "MrDeploymentActions|Stop environment" msgstr "" -msgid "MrList|Assigned to %{name}, go to their profile." +msgid "MrList|Assigned to %{name}" msgstr "" -msgid "MrList|Attention requested from assignee %{name}, go to their profile." +msgid "MrList|Attention requested from assignee %{name}" msgstr "" -msgid "MrList|Attention requested from reviewer %{name}, go to their profile." +msgid "MrList|Attention requested from reviewer %{name}" msgstr "" -msgid "MrList|Review requested from %{name}, go to their profile." +msgid "MrList|Review requested from %{name}" msgstr "" msgid "Multi-project" @@ -32367,6 +32373,9 @@ msgstr "" msgid "Runners|Never contacted" msgstr "" +msgid "Runners|Never contacted:" +msgstr "" + msgid "Runners|New group runners view" msgstr "" @@ -32388,12 +32397,18 @@ msgstr "" msgid "Runners|Offline runners" msgstr "" +msgid "Runners|Offline:" +msgstr "" + msgid "Runners|Online" msgstr "" msgid "Runners|Online runners" msgstr "" +msgid "Runners|Online:" +msgstr "" + msgid "Runners|Pause from accepting jobs" msgstr "" @@ -32462,9 +32477,18 @@ msgstr "" msgid "Runners|Runner cannot be deleted, please contact your administrator" msgstr "" +msgid "Runners|Runner has contacted GitLab within the last %{elapsedTime}" +msgstr "" + +msgid "Runners|Runner has never contacted GitLab (when you register a runner, use %{codeStart}gitlab-runner run%{codeEnd} to bring it online)" +msgstr "" + msgid "Runners|Runner has never contacted this instance" msgstr "" +msgid "Runners|Runner has not contacted GitLab in more than %{elapsedTime}" +msgstr "" + msgid "Runners|Runner is locked and available for currently assigned projects only. Only administrators can change the assigned projects." msgstr "" @@ -32492,6 +32516,9 @@ msgstr "" msgid "Runners|Runner registration" msgstr "" +msgid "Runners|Runner statuses" +msgstr "" + msgid "Runners|Runner unassigned from project." msgstr "" @@ -32522,6 +32549,9 @@ msgstr "" msgid "Runners|Stale runners" msgstr "" +msgid "Runners|Stale:" +msgstr "" + msgid "Runners|Status" msgstr "" @@ -32540,6 +32570,9 @@ msgstr "" msgid "Runners|The runner will be permanently deleted and no longer available for projects or groups in the instance. Are you sure you want to continue?" msgstr "" +msgid "Runners|This runner has not run any jobs." +msgstr "" + msgid "Runners|This runner is associated with specific projects." msgstr "" @@ -32618,9 +32651,6 @@ msgstr "" msgid "Runners|stale" msgstr "" -msgid "Runner|This runner has not run any jobs." -msgstr "" - msgid "Running" msgstr "" @@ -33792,6 +33822,9 @@ msgstr "" msgid "SecurityReports|Severity" msgstr "" +msgid "SecurityReports|Show %{pageSize} items" +msgstr "" + msgid "SecurityReports|Sometimes a scanner can't determine a finding's severity. Those findings may still be a potential source of risk though. Please review these manually." msgstr "" diff --git a/spec/components/diffs/stats_component_spec.rb b/spec/components/diffs/stats_component_spec.rb new file mode 100644 index 00000000000..2e5a5f2ca26 --- /dev/null +++ b/spec/components/diffs/stats_component_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Diffs::StatsComponent, type: :component do + include RepoHelpers + + subject(:component) do + described_class.new(diff_files: diff_files) + end + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:repository) { project.repository } + let_it_be(:commit) { project.commit(sample_commit.id) } + let_it_be(:diffs) { commit.raw_diffs } + let_it_be(:diff) { diffs.first } + let_it_be(:diff_refs) { commit.diff_refs } + let_it_be(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } + let_it_be(:diff_files) { [diff_file] } + + describe "rendered component" do + subject { rendered_component } + + let(:element) { page.find(".js-diff-stats-dropdown") } + + before do + render_inline component + end + + it { is_expected.to have_selector(".js-diff-stats-dropdown") } + + it "renders the data attributes" do + expect(element["data-changed"]).to eq("1") + expect(element["data-added"]).to eq("10") + expect(element["data-deleted"]).to eq("3") + + expect(Gitlab::Json.parse(element["data-files"])).to eq([{ + "href" => "##{Digest::SHA1.hexdigest(diff_file.file_path)}", + "title" => diff_file.new_path, + "name" => diff_file.file_path, + "path" => diff_file.file_path, + "icon" => "file-modified", + "iconColor" => "", + "added" => diff_file.added_lines, + "removed" => diff_file.removed_lines + }]) + end + end + + describe "#diff_file_path_text" do + it "returns full path by default" do + expect(subject.diff_file_path_text(diff_file)).to eq(diff_file.new_path) + end + + it "returns truncated path" do + expect(subject.diff_file_path_text(diff_file, max: 10)).to eq("...open.rb") + end + + it "returns the path if max is oddly small" do + expect(subject.diff_file_path_text(diff_file, max: 3)).to eq(diff_file.new_path) + end + + it "returns the path if max is oddly large" do + expect(subject.diff_file_path_text(diff_file, max: 100)).to eq(diff_file.new_path) + end + end +end diff --git a/spec/components/pajamas/alert_component_spec.rb b/spec/components/pajamas/alert_component_spec.rb new file mode 100644 index 00000000000..628d715ff64 --- /dev/null +++ b/spec/components/pajamas/alert_component_spec.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true +require "spec_helper" + +RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do + context 'with content' do + before do + render_inline(described_class.new) { '_content_' } + end + + it 'has content' do + expect(rendered_component).to have_text('_content_') + end + end + + context 'with defaults' do + before do + render_inline described_class.new + end + + it 'does not set a title' do + expect(rendered_component).not_to have_selector('.gl-alert-title') + expect(rendered_component).to have_selector('.gl-alert-icon-no-title') + end + + it 'renders the default variant' do + expect(rendered_component).to have_selector('.gl-alert-info') + expect(rendered_component).to have_selector("[data-testid='information-o-icon']") + end + + it 'renders a dismiss button' do + expect(rendered_component).to have_selector('.gl-dismiss-btn.js-close') + expect(rendered_component).to have_selector("[data-testid='close-icon']") + end + end + + context 'with custom options' do + context 'with simple options' do + context 'without dismissible content' do + before do + render_inline described_class.new( + title: '_title_', + dismissible: false, + alert_class: '_alert_class_', + alert_data: { + feature_id: '_feature_id_', + dismiss_endpoint: '_dismiss_endpoint_' + } + ) + end + + it 'sets the title' do + expect(rendered_component).to have_selector('.gl-alert-title') + expect(rendered_component).to have_content('_title_') + expect(rendered_component).not_to have_selector('.gl-alert-icon-no-title') + end + + it 'sets to not be dismissible' do + expect(rendered_component).not_to have_selector('.gl-dismiss-btn.js-close') + expect(rendered_component).not_to have_selector("[data-testid='close-icon']") + end + + it 'sets the alert_class' do + expect(rendered_component).to have_selector('._alert_class_') + end + + it 'sets the alert_data' do + expect(rendered_component).to have_selector('[data-feature-id="_feature_id_"][data-dismiss-endpoint="_dismiss_endpoint_"]') + end + end + end + + context 'with dismissible content' do + before do + render_inline described_class.new( + close_button_class: '_close_button_class_', + close_button_data: { + testid: '_close_button_testid_' + } + ) + end + + it 'renders a dismiss button and data' do + expect(rendered_component).to have_selector('.gl-dismiss-btn.js-close._close_button_class_') + expect(rendered_component).to have_selector("[data-testid='close-icon']") + expect(rendered_component).to have_selector('[data-testid="_close_button_testid_"]') + end + end + + context 'with setting variant type' do + where(:variant) { [:warning, :success, :danger, :tip] } + + before do + render_inline described_class.new(variant: variant) + end + + with_them do + it 'renders the variant' do + expect(rendered_component).to have_selector(".gl-alert-#{variant}") + expect(rendered_component).to have_selector("[data-testid='#{described_class::ICONS[variant]}-icon']") + end + end + end + end +end diff --git a/spec/controllers/projects/packages/infrastructure_registry_controller_spec.rb b/spec/controllers/projects/packages/infrastructure_registry_controller_spec.rb index a655c742973..fc741d0f3f6 100644 --- a/spec/controllers/projects/packages/infrastructure_registry_controller_spec.rb +++ b/spec/controllers/projects/packages/infrastructure_registry_controller_spec.rb @@ -41,17 +41,5 @@ RSpec.describe Projects::Packages::InfrastructureRegistryController do it_behaves_like 'returning response status', :not_found end - - context 'with package file pending destruction' do - let_it_be(:package_file_pending_destruction) { create(:package_file, :pending_destruction, package: terraform_module) } - - let(:terraform_module_package_file) { terraform_module.package_files.first } - - it 'does not return them' do - subject - - expect(assigns(:package_files)).to contain_exactly(terraform_module_package_file) - end - end end end diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb index f781ba0827c..a15b6072e70 100644 --- a/spec/features/merge_requests/user_mass_updates_spec.rb +++ b/spec/features/merge_requests/user_mass_updates_spec.rb @@ -70,7 +70,7 @@ RSpec.describe 'Merge requests > User mass updates', :js do it 'updates merge request with assignee' do change_assignee(user.name) - expect(find('.issuable-meta a.author-link')[:title]).to eq "Attention requested from assignee #{user.name}, go to their profile." + expect(find('.issuable-meta a.author-link')[:title]).to eq "Attention requested from assignee #{user.name}" end end end diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js index 6f6f50b89bc..b96dfc00c11 100644 --- a/spec/frontend/issues/show/components/description_spec.js +++ b/spec/frontend/issues/show/components/description_spec.js @@ -2,11 +2,13 @@ import $ from 'jquery'; import { nextTick } from 'vue'; import '~/behaviors/markdown/render_gfm'; import { GlTooltip, GlModal } from '@gitlab/ui'; +import setWindowLocation from 'helpers/set_window_location_helper'; import { stubComponent } from 'helpers/stub_component'; import { TEST_HOST } from 'helpers/test_constants'; import { mockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import Description from '~/issues/show/components/description.vue'; +import { updateHistory } from '~/lib/utils/url_utility'; import TaskList from '~/task_list'; import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; @@ -17,6 +19,10 @@ import { } from '../mock_data/mock_data'; jest.mock('~/flash'); +jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), + updateHistory: jest.fn(), +})); jest.mock('~/task_list'); const showModal = jest.fn(); @@ -55,6 +61,8 @@ describe('Description component', () => { } beforeEach(() => { + setWindowLocation(TEST_HOST); + if (!document.querySelector('.issuable-meta')) { const metaData = document.createElement('div'); metaData.classList.add('issuable-meta'); @@ -285,48 +293,86 @@ describe('Description component', () => { describe('work items detail', () => { const findTaskLink = () => wrapper.find('a.gfm-issue'); - beforeEach(() => { - createComponent({ - props: { - descriptionHtml: descriptionHtmlWithTask, - }, - provide: { - glFeatures: { workItems: true }, - }, + describe('when opening and closing', () => { + beforeEach(() => { + createComponent({ + props: { + descriptionHtml: descriptionHtmlWithTask, + }, + provide: { + glFeatures: { workItems: true }, + }, + }); + return nextTick(); }); - return nextTick(); - }); - it('opens when task button is clicked', async () => { - expect(findWorkItemDetailModal().props('visible')).toBe(false); + it('opens when task button is clicked', async () => { + expect(findWorkItemDetailModal().props('visible')).toBe(false); - await findTaskLink().trigger('click'); + await findTaskLink().trigger('click'); - expect(findWorkItemDetailModal().props('visible')).toBe(true); - }); + expect(findWorkItemDetailModal().props('visible')).toBe(true); + expect(updateHistory).toHaveBeenCalledWith({ + url: `${TEST_HOST}/?work_item_id=2`, + replace: true, + }); + }); - it('closes from an open state', async () => { - await findTaskLink().trigger('click'); + it('closes from an open state', async () => { + await findTaskLink().trigger('click'); - expect(findWorkItemDetailModal().props('visible')).toBe(true); + expect(findWorkItemDetailModal().props('visible')).toBe(true); - findWorkItemDetailModal().vm.$emit('close'); - await nextTick(); + findWorkItemDetailModal().vm.$emit('close'); + await nextTick(); - expect(findWorkItemDetailModal().props('visible')).toBe(false); - }); + expect(findWorkItemDetailModal().props('visible')).toBe(false); + expect(updateHistory).toHaveBeenLastCalledWith({ + url: `${TEST_HOST}/`, + replace: true, + }); + }); - it('tracks when opened', async () => { - const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + it('tracks when opened', async () => { + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - await findTaskLink().trigger('click'); + await findTaskLink().trigger('click'); - expect(trackingSpy).toHaveBeenCalledWith('workItems:show', 'viewed_work_item_from_modal', { - category: 'workItems:show', - label: 'work_item_view', - property: 'type_task', + expect(trackingSpy).toHaveBeenCalledWith( + 'workItems:show', + 'viewed_work_item_from_modal', + { + category: 'workItems:show', + label: 'work_item_view', + property: 'type_task', + }, + ); }); }); + + describe('when url query `work_item_id` exists', () => { + it.each` + behavior | workItemId | visible + ${'opens'} | ${'123'} | ${true} + ${'does not open'} | ${'123e'} | ${false} + ${'does not open'} | ${'12e3'} | ${false} + ${'does not open'} | ${'1e23'} | ${false} + ${'does not open'} | ${'x'} | ${false} + ${'does not open'} | ${'undefined'} | ${false} + `( + '$behavior when url contains `work_item_id=$workItemId`', + async ({ workItemId, visible }) => { + setWindowLocation(`?work_item_id=${workItemId}`); + + createComponent({ + props: { descriptionHtml: descriptionHtmlWithTask }, + provide: { glFeatures: { workItems: true } }, + }); + + expect(findWorkItemDetailModal().props('visible')).toBe(visible); + }, + ); + }); }); }); }); diff --git a/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js b/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js index 175896c4ab0..97d1b077164 100644 --- a/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js +++ b/spec/frontend/jira_connect/subscriptions/pages/sign_in_spec.js @@ -5,7 +5,7 @@ import SignInLegacyButton from '~/jira_connect/subscriptions/components/sign_in_ import SignInOauthButton from '~/jira_connect/subscriptions/components/sign_in_oauth_button.vue'; import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue'; import createStore from '~/jira_connect/subscriptions/store'; -import { I18N_DEFAULT_SIGN_IN_BUTTON_TEXT } from '../../../../../app/assets/javascripts/jira_connect/subscriptions/constants'; +import { I18N_DEFAULT_SIGN_IN_BUTTON_TEXT } from '~/jira_connect/subscriptions/constants'; jest.mock('~/jira_connect/subscriptions/utils'); diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js index edc25eb7e02..f8011860a49 100644 --- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js +++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js @@ -41,7 +41,13 @@ import adminRunnersCountQuery from '~/runner/graphql/list/admin_runners_count.qu import { captureException } from '~/runner/sentry_utils'; import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; -import { runnersData, runnersCountData, runnersDataPaginated } from '../mock_data'; +import { + runnersData, + runnersCountData, + runnersDataPaginated, + onlineContactTimeoutSecs, + staleTimeoutSecs, +} from '../mock_data'; const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN'; const mockRunners = runnersData.data.runners.nodes; @@ -94,6 +100,8 @@ describe('AdminRunnersApp', () => { }, provide: { localMutations, + onlineContactTimeoutSecs, + staleTimeoutSecs, ...provide, }, ...options, diff --git a/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap b/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap new file mode 100644 index 00000000000..80a04401760 --- /dev/null +++ b/spec/frontend/runner/components/__snapshots__/runner_status_popover_spec.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RunnerStatusPopover renders complete text 1`] = `"Never contacted: Runner has never contacted GitLab (when you register a runner, use gitlab-runner run to bring it online) Online: Runner has contacted GitLab within the last 2 hours Offline: Runner has not contacted GitLab in more than 2 hours Stale: Runner has not contacted GitLab in more than 2 months"`; diff --git a/spec/frontend/runner/components/runner_list_spec.js b/spec/frontend/runner/components/runner_list_spec.js index fab94771990..872394430ae 100644 --- a/spec/frontend/runner/components/runner_list_spec.js +++ b/spec/frontend/runner/components/runner_list_spec.js @@ -6,7 +6,8 @@ import { } from 'helpers/vue_test_utils_helper'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import RunnerList from '~/runner/components/runner_list.vue'; -import { runnersData } from '../mock_data'; +import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue'; +import { runnersData, onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data'; const mockRunners = runnersData.data.runners.nodes; const mockActiveRunnersCount = mockRunners.length; @@ -28,21 +29,34 @@ describe('RunnerList', () => { activeRunnersCount: mockActiveRunnersCount, ...props, }, + provide: { + onlineContactTimeoutSecs, + staleTimeoutSecs, + }, ...options, }); }; - beforeEach(() => { - createComponent({}, mountExtended); - }); - afterEach(() => { wrapper.destroy(); }); it('Displays headers', () => { + createComponent( + { + stubs: { + RunnerStatusPopover: { + template: '<div/>', + }, + }, + }, + mountExtended, + ); + const headerLabels = findHeaders().wrappers.map((w) => w.text()); + expect(findHeaders().at(0).findComponent(RunnerStatusPopover).exists()).toBe(true); + expect(headerLabels).toEqual([ 'Status', 'Runner', @@ -61,6 +75,8 @@ describe('RunnerList', () => { }); it('Displays a list of runners', () => { + createComponent({}, mountExtended); + expect(findRows()).toHaveLength(4); expect(findSkeletonLoader().exists()).toBe(false); @@ -69,6 +85,8 @@ describe('RunnerList', () => { it('Displays details of a runner', () => { const { id, description, version, shortSha } = mockRunners[0]; + createComponent({}, mountExtended); + // Badges expect(findCell({ fieldKey: 'status' }).text()).toMatchInterpolatedText( 'never contacted paused', @@ -183,6 +201,8 @@ describe('RunnerList', () => { const { id, shortSha } = mockRunners[0]; const numericId = getIdFromGraphQLId(id); + createComponent({}, mountExtended); + expect(findCell({ fieldKey: 'summary' }).text()).toContain(`#${numericId} (${shortSha})`); }); diff --git a/spec/frontend/runner/components/runner_status_popover_spec.js b/spec/frontend/runner/components/runner_status_popover_spec.js new file mode 100644 index 00000000000..789283d1245 --- /dev/null +++ b/spec/frontend/runner/components/runner_status_popover_spec.js @@ -0,0 +1,36 @@ +import { GlSprintf } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import RunnerStatusPopover from '~/runner/components/runner_status_popover.vue'; +import HelpPopover from '~/vue_shared/components/help_popover.vue'; +import { onlineContactTimeoutSecs, staleTimeoutSecs } from '../mock_data'; + +describe('RunnerStatusPopover', () => { + let wrapper; + + const createComponent = ({ provide = {} } = {}) => { + wrapper = shallowMountExtended(RunnerStatusPopover, { + provide: { + onlineContactTimeoutSecs, + staleTimeoutSecs, + ...provide, + }, + stubs: { + GlSprintf, + }, + }); + }; + + const findHelpPopover = () => wrapper.findComponent(HelpPopover); + + it('renders popoover', () => { + createComponent(); + + expect(findHelpPopover().exists()).toBe(true); + }); + + it('renders complete text', () => { + createComponent(); + + expect(findHelpPopover().text()).toMatchSnapshot(); + }); +}); diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js index 6d7ecc4506a..706f46a7c56 100644 --- a/spec/frontend/runner/group_runners/group_runners_app_spec.js +++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js @@ -38,7 +38,13 @@ import getGroupRunnersCountQuery from '~/runner/graphql/list/group_runners_count import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue'; import { captureException } from '~/runner/sentry_utils'; import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; -import { groupRunnersData, groupRunnersDataPaginated, groupRunnersCountData } from '../mock_data'; +import { + groupRunnersData, + groupRunnersDataPaginated, + groupRunnersCountData, + onlineContactTimeoutSecs, + staleTimeoutSecs, +} from '../mock_data'; Vue.use(VueApollo); Vue.use(GlToast); @@ -90,6 +96,10 @@ describe('GroupRunnersApp', () => { groupRunnersLimitedCount: mockGroupRunnersLimitedCount, ...props, }, + provide: { + onlineContactTimeoutSecs, + staleTimeoutSecs, + }, }); }; diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js index 49c25039719..fbe8926124c 100644 --- a/spec/frontend/runner/mock_data.js +++ b/spec/frontend/runner/mock_data.js @@ -14,6 +14,10 @@ import runnerWithGroupData from 'test_fixtures/graphql/runner/details/runner.que import runnerProjectsData from 'test_fixtures/graphql/runner/details/runner_projects.query.graphql.json'; import runnerJobsData from 'test_fixtures/graphql/runner/details/runner_jobs.query.graphql.json'; +// Other mock data +export const onlineContactTimeoutSecs = 2 * 60 * 60; +export const staleTimeoutSecs = 5259492; // Ruby's `2.months` + export { runnersData, runnersCountData, diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb index dd1804e2078..0046d481282 100644 --- a/spec/helpers/ci/runners_helper_spec.rb +++ b/spec/helpers/ci/runners_helper_spec.rb @@ -86,7 +86,9 @@ RSpec.describe Ci::RunnersHelper do it 'returns the data in format' do expect(helper.admin_runners_data_attributes).to eq({ runner_install_help_page: 'https://docs.gitlab.com/runner/install/', - registration_token: Gitlab::CurrentSettings.runners_registration_token + registration_token: Gitlab::CurrentSettings.runners_registration_token, + online_contact_timeout_secs: 7200, + stale_timeout_secs: 7889238 }) end end @@ -128,12 +130,14 @@ RSpec.describe Ci::RunnersHelper do let(:group) { create(:group) } it 'returns group data to render a runner list' do - data = helper.group_runners_data_attributes(group) - - expect(data[:registration_token]).to eq(group.runners_token) - expect(data[:group_id]).to eq(group.id) - expect(data[:group_full_path]).to eq(group.full_path) - expect(data[:runner_install_help_page]).to eq('https://docs.gitlab.com/runner/install/') + expect(helper.group_runners_data_attributes(group)).to eq({ + registration_token: group.runners_token, + group_id: group.id, + group_full_path: group.full_path, + runner_install_help_page: 'https://docs.gitlab.com/runner/install/', + online_contact_timeout_secs: 7200, + stale_timeout_secs: 7889238 + }) end end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index ccc2db00236..84e702cd6a9 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -425,16 +425,6 @@ RSpec.describe DiffHelper do end end - describe '#diff_file_path_text' do - it 'returns full path by default' do - expect(diff_file_path_text(diff_file)).to eq(diff_file.new_path) - end - - it 'returns truncated path' do - expect(diff_file_path_text(diff_file, max: 10)).to eq("...open.rb") - end - end - describe "#collapsed_diff_url" do let(:params) do { diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb index 90ca5430526..3fa535dd800 100644 --- a/spec/lib/gitlab/data_builder/note_spec.rb +++ b/spec/lib/gitlab/data_builder/note_spec.rb @@ -8,18 +8,22 @@ RSpec.describe Gitlab::DataBuilder::Note do let(:data) { described_class.build(note, user) } let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors - before do - expect(data).to have_key(:object_attributes) - expect(data[:object_attributes]).to have_key(:url) - expect(data[:object_attributes][:url]) - .to eq(Gitlab::UrlBuilder.build(note)) - expect(data[:object_kind]).to eq('note') - expect(data[:user]).to eq(user.hook_attrs) + shared_examples 'includes general data' do + specify do + expect(data).to have_key(:object_attributes) + expect(data[:object_attributes]).to have_key(:url) + expect(data[:object_attributes][:url]) + .to eq(Gitlab::UrlBuilder.build(note)) + expect(data[:object_kind]).to eq('note') + expect(data[:user]).to eq(user.hook_attrs) + end end describe 'When asking for a note on commit' do let(:note) { create(:note_on_commit, project: project) } + it_behaves_like 'includes general data' + it 'returns the note and commit-specific data' do expect(data).to have_key(:commit) end @@ -31,6 +35,8 @@ RSpec.describe Gitlab::DataBuilder::Note do describe 'When asking for a note on commit diff' do let(:note) { create(:diff_note_on_commit, project: project) } + it_behaves_like 'includes general data' + it 'returns the note and commit-specific data' do expect(data).to have_key(:commit) end @@ -51,22 +57,21 @@ RSpec.describe Gitlab::DataBuilder::Note do create(:note_on_issue, noteable: issue, project: project) end + it_behaves_like 'includes general data' + it 'returns the note and issue-specific data' do - without_timestamps = lambda { |label| label.except('created_at', 'updated_at') } - hook_attrs = issue.reload.hook_attrs + expect_next_instance_of(Gitlab::HookData::IssueBuilder) do |issue_data_builder| + expect(issue_data_builder).to receive(:build).and_return('Issue data') + end - expect(data).to have_key(:issue) - expect(data[:issue].except('updated_at', 'labels')) - .to eq(hook_attrs.except('updated_at', 'labels')) - expect(data[:issue]['updated_at']) - .to be >= hook_attrs['updated_at'] - expect(data[:issue]['labels'].map(&without_timestamps)) - .to eq(hook_attrs['labels'].map(&without_timestamps)) + expect(data[:issue]).to eq('Issue data') end context 'with confidential issue' do let(:issue) { create(:issue, project: project, confidential: true) } + it_behaves_like 'includes general data' + it 'sets event_type to confidential_note' do expect(data[:event_type]).to eq('confidential_note') end @@ -77,10 +82,12 @@ RSpec.describe Gitlab::DataBuilder::Note do end describe 'When asking for a note on merge request' do + let(:label) { create(:label, project: project) } let(:merge_request) do - create(:merge_request, created_at: fixed_time, + create(:labeled_merge_request, created_at: fixed_time, updated_at: fixed_time, - source_project: project) + source_project: project, + labels: [label]) end let(:note) do @@ -88,12 +95,14 @@ RSpec.describe Gitlab::DataBuilder::Note do project: project) end - it 'returns the note and merge request data' do - expect(data).to have_key(:merge_request) - expect(data[:merge_request].except('updated_at')) - .to eq(merge_request.reload.hook_attrs.except('updated_at')) - expect(data[:merge_request]['updated_at']) - .to be >= merge_request.hook_attrs['updated_at'] + it_behaves_like 'includes general data' + + it 'returns the merge request data' do + expect_next_instance_of(Gitlab::HookData::MergeRequestBuilder) do |mr_data_builder| + expect(mr_data_builder).to receive(:build).and_return('MR data') + end + + expect(data[:merge_request]).to eq('MR data') end include_examples 'project hook data' @@ -101,9 +110,11 @@ RSpec.describe Gitlab::DataBuilder::Note do end describe 'When asking for a note on merge request diff' do + let(:label) { create(:label, project: project) } let(:merge_request) do - create(:merge_request, created_at: fixed_time, updated_at: fixed_time, - source_project: project) + create(:labeled_merge_request, created_at: fixed_time, updated_at: fixed_time, + source_project: project, + labels: [label]) end let(:note) do @@ -111,12 +122,14 @@ RSpec.describe Gitlab::DataBuilder::Note do project: project) end - it 'returns the note and merge request diff data' do - expect(data).to have_key(:merge_request) - expect(data[:merge_request].except('updated_at')) - .to eq(merge_request.reload.hook_attrs.except('updated_at')) - expect(data[:merge_request]['updated_at']) - .to be >= merge_request.hook_attrs['updated_at'] + it_behaves_like 'includes general data' + + it 'returns the merge request data' do + expect_next_instance_of(Gitlab::HookData::MergeRequestBuilder) do |mr_data_builder| + expect(mr_data_builder).to receive(:build).and_return('MR data') + end + + expect(data[:merge_request]).to eq('MR data') end include_examples 'project hook data' @@ -134,6 +147,8 @@ RSpec.describe Gitlab::DataBuilder::Note do project: project) end + it_behaves_like 'includes general data' + it 'returns the note and project snippet data' do expect(data).to have_key(:snippet) expect(data[:snippet].except('updated_at')) diff --git a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb index f5ee8eba8bc..676396697fb 100644 --- a/spec/lib/gitlab/hook_data/issuable_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issuable_builder_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Gitlab::HookData::IssuableBuilder do let_it_be(:user) { create(:user) } # This shared example requires a `builder` and `user` variable - shared_examples 'issuable hook data' do |kind| + shared_examples 'issuable hook data' do |kind, hook_data_issuable_builder_class| let(:data) { builder.build(user: user) } include_examples 'project hook data' do @@ -20,7 +20,7 @@ RSpec.describe Gitlab::HookData::IssuableBuilder do expect(data[:object_kind]).to eq(kind) expect(data[:user]).to eq(user.hook_attrs) expect(data[:project]).to eq(builder.issuable.project.hook_attrs) - expect(data[:object_attributes]).to eq(builder.issuable.hook_attrs) + expect(data[:object_attributes]).to eq(hook_data_issuable_builder_class.new(issuable).build) expect(data[:changes]).to eq({}) expect(data[:repository]).to eq(builder.issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)) end @@ -95,12 +95,12 @@ RSpec.describe Gitlab::HookData::IssuableBuilder do end describe '#build' do - it_behaves_like 'issuable hook data', 'issue' do + it_behaves_like 'issuable hook data', 'issue', Gitlab::HookData::IssueBuilder do let(:issuable) { create(:issue, description: 'A description') } let(:builder) { described_class.new(issuable) } end - it_behaves_like 'issuable hook data', 'merge_request' do + it_behaves_like 'issuable hook data', 'merge_request', Gitlab::HookData::MergeRequestBuilder do let(:issuable) { create(:merge_request, description: 'A description') } let(:builder) { described_class.new(issuable) } end diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb index ddd681f75f0..771fc0218e2 100644 --- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -62,6 +62,7 @@ RSpec.describe Gitlab::HookData::MergeRequestBuilder do expect(data).to include(:human_time_estimate) expect(data).to include(:human_total_time_spent) expect(data).to include(:human_time_change) + expect(data).to include(:labels) end context 'when the MR has an image in the description' do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index e3c0e3a7a2b..b38135fc0b2 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -625,6 +625,16 @@ RSpec.describe Issuable do end end + describe "#labels_hook_attrs" do + let(:project) { create(:project) } + let(:label) { create(:label) } + let(:issue) { create(:labeled_issue, project: project, labels: [label]) } + + it "returns a list of label hook attributes" do + expect(issue.labels_hook_attrs).to match_array([label.hook_attrs]) + end + end + describe '.labels_hash' do let(:feature_label) { create(:label, title: 'Feature') } let(:second_label) { create(:label, title: 'Second Label') } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 61ad9dc26be..fe09dadd0db 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -1172,18 +1172,6 @@ RSpec.describe Issue do end end - describe '#hook_attrs' do - it 'delegates to Gitlab::HookData::IssueBuilder#build' do - builder = double - - expect(Gitlab::HookData::IssueBuilder) - .to receive(:new).with(subject).and_return(builder) - expect(builder).to receive(:build) - - subject.hook_attrs - end - end - describe '#check_for_spam?' do let_it_be(:support_bot) { ::User.support_bot } @@ -1332,15 +1320,6 @@ RSpec.describe Issue do subject { create(:issue, updated_at: 1.hour.ago) } end - describe "#labels_hook_attrs" do - let(:label) { create(:label) } - let(:issue) { create(:labeled_issue, project: reusable_project, labels: [label]) } - - it "returns a list of label hook attributes" do - expect(issue.labels_hook_attrs).to eq([label.hook_attrs]) - end - end - context "relative positioning" do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 0d15851e583..9d734e85668 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1757,18 +1757,6 @@ RSpec.describe MergeRequest, factory_default: :keep do end end - describe '#hook_attrs' do - it 'delegates to Gitlab::HookData::MergeRequestBuilder#build' do - builder = double - - expect(Gitlab::HookData::MergeRequestBuilder) - .to receive(:new).with(subject).and_return(builder) - expect(builder).to receive(:build) - - subject.hook_attrs - end - end - describe '#diverged_commits_count' do let(:project) { create(:project, :repository) } let(:forked_project) { fork_project(project, nil, repository: true) } diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb index 4ad2446f8d0..6d84b2c6931 100644 --- a/spec/models/project_import_state_spec.rb +++ b/spec/models/project_import_state_spec.rb @@ -89,19 +89,6 @@ RSpec.describe ProjectImportState, type: :model do import_state.mark_as_failed(error_message) end.to change { project.reload.import_data }.from(import_data).to(nil) end - - context 'when remove_import_data_on_failure feature flag is disabled' do - it 'removes project import data' do - stub_feature_flags(remove_import_data_on_failure: false) - - project = create(:project, import_data: ProjectImportData.new(data: { 'test' => 'some data' })) - import_state = create(:import_state, :started, project: project) - - expect do - import_state.mark_as_failed(error_message) - end.not_to change { project.reload.import_data } - end - end end describe '#human_status_name' do diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb deleted file mode 100644 index 13ffc1b7f87..00000000000 --- a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -# This shared example requires a `builder` and `user` variable -RSpec.shared_examples 'issuable hook data' do |kind| - let(:data) { builder.build(user: user) } - - include_examples 'project hook data' do - let(:project) { builder.issuable.project } - end - - include_examples 'deprecated repository hook data' - - context "with a #{kind}" do - it 'contains issuable data' do - expect(data[:object_kind]).to eq(kind) - expect(data[:user]).to eq(user.hook_attrs) - expect(data[:project]).to eq(builder.issuable.project.hook_attrs) - expect(data[:object_attributes]).to eq(builder.issuable.hook_attrs) - expect(data[:changes]).to eq({}) - expect(data[:repository]).to eq(builder.issuable.project.hook_attrs.slice(:name, :url, :description, :homepage)) - end - - it 'does not contain certain keys' do - expect(data).not_to have_key(:assignees) - expect(data).not_to have_key(:assignee) - end - - describe 'changes are given' do - let(:changes) do - { - cached_markdown_version: %w[foo bar], - description: ['A description', 'A cool description'], - description_html: %w[foo bar], - in_progress_merge_commit_sha: %w[foo bar], - lock_version: %w[foo bar], - merge_jid: %w[foo bar], - title: ['A title', 'Hello World'], - title_html: %w[foo bar] - } - end - - let(:data) { builder.build(user: user, changes: changes) } - - it 'populates the :changes hash' do - expect(data[:changes]).to match(hash_including({ - title: { previous: 'A title', current: 'Hello World' }, - description: { previous: 'A description', current: 'A cool description' } - })) - end - - it 'does not contain certain keys' do - expect(data[:changes]).not_to have_key('cached_markdown_version') - expect(data[:changes]).not_to have_key('description_html') - expect(data[:changes]).not_to have_key('lock_version') - expect(data[:changes]).not_to have_key('title_html') - expect(data[:changes]).not_to have_key('in_progress_merge_commit_sha') - expect(data[:changes]).not_to have_key('merge_jid') - end - end - end -end diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb index 95471203983..7677e5d8cb2 100644 --- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb +++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb @@ -154,6 +154,30 @@ RSpec.shared_examples 'logs an auth warning' do |requested_actions| end end +RSpec.shared_examples 'allowed to delete container repository images' do + let(:authentication_abilities) do + [:admin_container_image] + end + + it_behaves_like 'a valid token' + + context 'allow to delete images' do + let(:current_params) do + { scopes: ["repository:#{project.full_path}:*"] } + end + + it_behaves_like 'a deletable' + end + + context 'allow to delete images since registry 2.7' do + let(:current_params) do + { scopes: ["repository:#{project.full_path}:delete"] } + end + + it_behaves_like 'a deletable since registry 2.7' + end +end + RSpec.shared_examples 'a container registry auth service' do include_context 'container registry auth service context' @@ -544,38 +568,14 @@ RSpec.shared_examples 'a container registry auth service' do end context 'delete authorized as maintainer' do - let_it_be(:current_project) { create(:project) } + let_it_be(:project) { create(:project) } let_it_be(:current_user) { create(:user) } - let(:authentication_abilities) do - [:admin_container_image] - end - before_all do - current_project.add_maintainer(current_user) - end - - it_behaves_like 'a valid token' - - context 'allow to delete images' do - let(:current_params) do - { scopes: ["repository:#{current_project.full_path}:*"] } - end - - it_behaves_like 'a deletable' do - let(:project) { current_project } - end + project.add_maintainer(current_user) end - context 'allow to delete images since registry 2.7' do - let(:current_params) do - { scopes: ["repository:#{current_project.full_path}:delete"] } - end - - it_behaves_like 'a deletable since registry 2.7' do - let(:project) { current_project } - end - end + it_behaves_like 'allowed to delete container repository images' end context 'build authorized as user' do |