Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-03-31 15:08:32 +00:00
parent 5820d448c1
commit 85ea3dd4f4
72 changed files with 877 additions and 407 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
.js-diff-stats-dropdown{ data: { changed: @changed, added: @added, deleted: @removed, files: diff_files_data } }

View File

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

View File

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

View File

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

View File

@ -9,7 +9,6 @@ module Projects
def show
@package = project.packages.find(params[:id])
@package_files = @package.installable_package_files.recent
end
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,47 +293,85 @@ 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);
});
it('closes from an open state', async () => {
await findTaskLink().trigger('click');
expect(findWorkItemDetailModal().props('visible')).toBe(true);
findWorkItemDetailModal().vm.$emit('close');
await nextTick();
expect(findWorkItemDetailModal().props('visible')).toBe(false);
});
it('tracks when opened', async () => {
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
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(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');
expect(findWorkItemDetailModal().props('visible')).toBe(true);
findWorkItemDetailModal().vm.$emit('close');
await nextTick();
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);
await findTaskLink().trigger('click');
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);
},
);
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 'returns the note and issue-specific data' do
without_timestamps = lambda { |label| label.except('created_at', 'updated_at') }
hook_attrs = issue.reload.hook_attrs
it_behaves_like 'includes general data'
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))
it 'returns the note and issue-specific data' do
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[: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'))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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