Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-04-27 12:16:04 +00:00
parent 9966836575
commit 5cb0fa35e7
78 changed files with 674 additions and 370 deletions

View File

@ -1,5 +1,4 @@
<script>
import AWS_LOGO_URL from '@gitlab/svgs/dist/illustrations/logos/aws.svg?url';
import DOCKER_LOGO_URL from '@gitlab/svgs/dist/illustrations/third-party-logos/ci_cd-template-logos/docker.png';
import KUBERNETES_LOGO_URL from '@gitlab/svgs/dist/illustrations/logos/kubernetes.svg?url';
import { GlFormRadioGroup, GlIcon, GlLink } from '@gitlab/ui';
@ -8,7 +7,6 @@ import {
LINUX_PLATFORM,
MACOS_PLATFORM,
WINDOWS_PLATFORM,
AWS_PLATFORM,
DOCKER_HELP_URL,
KUBERNETES_HELP_URL,
} from '../constants';
@ -43,8 +41,6 @@ export default {
MACOS_PLATFORM,
WINDOWS_PLATFORM,
AWS_PLATFORM,
AWS_LOGO_URL,
DOCKER_HELP_URL,
DOCKER_LOGO_URL,
KUBERNETES_HELP_URL,
@ -71,20 +67,6 @@ export default {
</div>
</div>
<div class="gl-mt-3 gl-mb-6">
<label>{{ s__('Runners|Cloud templates') }}</label>
<!-- eslint-disable @gitlab/vue-require-i18n-strings -->
<div class="gl-display-flex gl-flex-wrap gl-gap-5">
<runner-platforms-radio
v-model="model"
:image="$options.AWS_LOGO_URL"
:value="$options.AWS_PLATFORM"
>
AWS
</runner-platforms-radio>
</div>
</div>
<div class="gl-mt-3 gl-mb-6">
<label>{{ s__('Runners|Containers') }}</label>

View File

@ -195,7 +195,6 @@ export const GROUP_FILTERED_SEARCH_NAMESPACE = 'group_runners';
export const LINUX_PLATFORM = 'linux';
export const MACOS_PLATFORM = 'osx';
export const WINDOWS_PLATFORM = 'windows';
export const AWS_PLATFORM = 'aws';
export const DOWNLOAD_LOCATIONS = {
[LINUX_PLATFORM]: [

View File

@ -101,8 +101,10 @@ export default (IssuableTokenKeys, disableTargetBranchFilter = false) => {
],
};
IssuableTokenKeys.tokenKeys.splice(3, 0, approvedToken.token);
IssuableTokenKeys.conditions.push(...approvedToken.conditions);
if (gon.features.mrApprovedFilter) {
IssuableTokenKeys.tokenKeys.splice(3, 0, approvedToken.token);
IssuableTokenKeys.conditions.push(...approvedToken.conditions);
}
const approvedBy = {
token: {
@ -149,7 +151,7 @@ export default (IssuableTokenKeys, disableTargetBranchFilter = false) => {
],
};
const tokenPosition = 4;
const tokenPosition = gon.features.mrApprovedFilter ? 4 : 3;
IssuableTokenKeys.tokenKeys.splice(tokenPosition, 0, approvedBy.token);
IssuableTokenKeys.tokenKeysWithAlternative.splice(
tokenPosition,

View File

@ -1,12 +1,5 @@
<script>
import {
GlButtonGroup,
GlButton,
GlDropdown,
GlDropdownItem,
GlTooltipDirective,
GlModalDirective,
} from '@gitlab/ui';
import { GlDropdownItem, GlModalDirective } from '@gitlab/ui';
import { TYPE_ISSUE } from '~/issues/constants';
import { __ } from '~/locale';
import CsvExportModal from './csv_export_modal.vue';
@ -17,19 +10,13 @@ export default {
exportAsCsvButtonText: __('Export as CSV'),
importCsvText: __('Import CSV'),
importFromJiraText: __('Import from Jira'),
importIssuesText: __('Import issues'),
},
name: 'CsvImportExportButtons',
components: {
GlButtonGroup,
GlButton,
GlDropdown,
GlDropdownItem,
CsvExportModal,
CsvImportModal,
},
directives: {
GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
},
inject: {
@ -42,18 +29,12 @@ export default {
showImportButton: {
default: false,
},
containerClass: {
default: '',
},
canEdit: {
default: false,
},
projectImportJiraPath: {
default: null,
},
showLabel: {
default: false,
},
},
props: {
exportCsvPath: {
@ -74,48 +55,30 @@ export default {
importModalId() {
return `${this.issuableType}-import-modal`;
},
importButtonTooltipText() {
return this.showLabel ? null : this.$options.i18n.importIssuesText;
},
importButtonIcon() {
return this.showLabel ? null : 'import';
},
},
};
</script>
<template>
<div :class="containerClass">
<gl-button-group class="gl-w-full">
<gl-button
v-if="showExportButton"
v-gl-tooltip="$options.i18n.exportAsCsvButtonText"
v-gl-modal="exportModalId"
icon="export"
:aria-label="$options.i18n.exportAsCsvButtonText"
data-qa-selector="export_as_csv_button"
/>
<gl-dropdown
v-if="showImportButton"
v-gl-tooltip="importButtonTooltipText"
data-qa-selector="import_issues_dropdown"
:text="$options.i18n.importIssuesText"
:text-sr-only="!showLabel"
:icon="importButtonIcon"
class="gl-w-full gl-md-w-auto"
>
<gl-dropdown-item v-gl-modal="importModalId">
{{ $options.i18n.importCsvText }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="canEdit"
:href="projectImportJiraPath"
data-qa-selector="import_from_jira_link"
>
{{ $options.i18n.importFromJiraText }}
</gl-dropdown-item>
</gl-dropdown>
</gl-button-group>
<ul class="gl-display-contents">
<gl-dropdown-item
v-if="showExportButton"
v-gl-modal="exportModalId"
data-qa-selector="export_as_csv_button"
>
{{ $options.i18n.exportAsCsvButtonText }}
</gl-dropdown-item>
<gl-dropdown-item v-if="showImportButton" v-gl-modal="importModalId">
{{ $options.i18n.importCsvText }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="showImportButton && canEdit"
:href="projectImportJiraPath"
data-qa-selector="import_from_jira_link"
>
{{ $options.i18n.importFromJiraText }}
</gl-dropdown-item>
<csv-export-modal
v-if="showExportButton"
:modal-id="exportModalId"
@ -123,5 +86,5 @@ export default {
:issuable-count="issuableCount"
/>
<csv-import-modal v-if="showImportButton" :modal-id="importModalId" />
</div>
</ul>
</template>

View File

@ -36,11 +36,9 @@ export function initCsvImportExportButtons() {
email,
exportCsvPath,
importCsvIssuesPath,
containerClass,
canEdit,
projectImportJiraPath,
maxAttachmentSize,
showLabel,
} = el.dataset;
return new Vue({
@ -52,11 +50,9 @@ export function initCsvImportExportButtons() {
issuableType,
email,
importCsvIssuesPath,
containerClass,
canEdit: parseBoolean(canEdit),
projectImportJiraPath,
maxAttachmentSize,
showLabel,
},
render: (createElement) =>
createElement(CsvImportExportButtons, {

View File

@ -1,5 +1,5 @@
<script>
import { GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import { GlButton, GlDropdown, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue';
@ -12,6 +12,7 @@ export default {
components: {
CsvImportExportButtons,
GlButton,
GlDropdown,
GlEmptyState,
GlLink,
GlSprintf,
@ -71,12 +72,19 @@ export default {
<gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
{{ $options.i18n.newIssueLabel }}
</gl-button>
<csv-import-export-buttons
<gl-dropdown
v-if="showCsvButtons"
class="gl-w-full gl-sm-w-auto gl-sm-mr-3"
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
/>
:text="$options.i18n.importIssues"
data-qa-selector="import_issues_dropdown"
>
<csv-import-export-buttons
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
/>
</gl-dropdown>
<new-resource-dropdown
v-if="showNewIssueDropdown"
class="gl-align-self-center"

View File

@ -1,5 +1,12 @@
<script>
import { GlButton, GlFilteredSearchToken, GlTooltipDirective } from '@gitlab/ui';
import {
GlButton,
GlFilteredSearchToken,
GlTooltipDirective,
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { isEmpty } from 'lodash';
@ -9,8 +16,8 @@ import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.grap
import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql';
import { createAlert, VARIANT_INFO } from '~/alert';
import { TYPENAME_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
import {
STATUS_ALL,
@ -114,6 +121,9 @@ export default {
EmptyStateWithAnyIssues,
EmptyStateWithoutAnyIssues,
GlButton,
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
IssuableByEmail,
IssuableList,
IssueCardStatistics,
@ -801,26 +811,6 @@ export default {
@page-size-change="handlePageSizeChange"
>
<template #nav-actions>
<gl-button
v-gl-tooltip
:href="rssPath"
icon="rss"
:title="$options.i18n.rssLabel"
:aria-label="$options.i18n.rssLabel"
/>
<gl-button
v-gl-tooltip
:href="calendarPath"
icon="calendar"
:title="$options.i18n.calendarLabel"
:aria-label="$options.i18n.calendarLabel"
/>
<csv-import-export-buttons
v-if="showCsvButtons"
class="gl-md-mr-3"
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
/>
<gl-button
v-if="canBulkUpdate"
:disabled="isBulkEditButtonDisabled"
@ -842,6 +832,30 @@ export default {
:query-variables="newIssueDropdownQueryVariables"
:extract-projects="extractProjects"
/>
<gl-dropdown
v-gl-tooltip.hover="$options.i18n.actionsLabel"
category="tertiary"
icon="ellipsis_v"
no-caret
:text="$options.i18n.actionsLabel"
text-sr-only
data-qa-selector="issues_list_more_actions_dropdown"
>
<csv-import-export-buttons
v-if="showCsvButtons"
:export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount"
/>
<gl-dropdown-divider v-if="showCsvButtons" />
<gl-dropdown-item :href="rssPath">
{{ $options.i18n.rssLabel }}
</gl-dropdown-item>
<gl-dropdown-item :href="calendarPath">
{{ $options.i18n.calendarLabel }}
</gl-dropdown-item>
</gl-dropdown>
</template>
<template #timeframe="{ issuable = {} }">

View File

@ -85,6 +85,7 @@ export const i18n = {
editIssues: __('Edit issues'),
errorFetchingCounts: __('An error occurred while getting issue counts'),
errorFetchingIssues: __('An error occurred while loading issues'),
importIssues: __('Import issues'),
issueRepositioningMessage: __(
'Issues are being rebalanced at the moment, so manual reordering is disabled.',
),

View File

@ -1,5 +1,5 @@
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import eventHub from '../event_hub';
@ -10,38 +10,48 @@ export default {
taskActions: s__('WorkItem|Task actions'),
},
components: {
GlDropdown,
GlDropdownItem,
GlDisclosureDropdown,
GlDisclosureDropdownItem,
},
inject: ['canUpdate'],
methods: {
convertToTask() {
eventHub.$emit('convert-task-list-item', this.$el.closest('li').dataset.sourcepos);
this.closeDropdown();
},
deleteTaskListItem() {
eventHub.$emit('delete-task-list-item', this.$el.closest('li').dataset.sourcepos);
this.closeDropdown();
},
closeDropdown() {
this.$refs.dropdown.close();
},
},
};
</script>
<template>
<gl-dropdown
<gl-disclosure-dropdown
v-if="canUpdate"
ref="dropdown"
class="task-list-item-actions-wrapper"
category="tertiary"
icon="ellipsis_v"
lazy
no-caret
right
:text="$options.i18n.taskActions"
placement="right"
:toggle-text="$options.i18n.taskActions"
text-sr-only
toggle-class="task-list-item-actions gl-opacity-0 gl-p-2!"
toggle-class="task-list-item-actions gl-opacity-0 gl-p-2! "
>
<gl-dropdown-item v-if="canUpdate" @click="convertToTask">
{{ $options.i18n.convertToTask }}
</gl-dropdown-item>
<gl-dropdown-item v-if="canUpdate" variant="danger" @click="deleteTaskListItem">
{{ $options.i18n.delete }}
</gl-dropdown-item>
</gl-dropdown>
<gl-disclosure-dropdown-item class="gl-ml-2!" @action="convertToTask">
<template #list-item>
{{ $options.i18n.convertToTask }}
</template>
</gl-disclosure-dropdown-item>
<gl-disclosure-dropdown-item class="gl-ml-2!" @action="deleteTaskListItem">
<template #list-item>
<span class="gl-text-red-500!">{{ $options.i18n.delete }}</span>
</template>
</gl-disclosure-dropdown-item>
</gl-disclosure-dropdown>
</template>

View File

@ -4,6 +4,7 @@ import { NAME_SORT_FIELD } from './common';
// Translations strings
export const CONTAINER_REGISTRY_TITLE = s__('ContainerRegistry|Container Registry');
export const SETTINGS_TEXT = s__('ContainerRegistry|Configure in settings');
export const CONNECTION_ERROR_TITLE = s__('ContainerRegistry|Docker connection error');
export const CONNECTION_ERROR_MESSAGE = s__(
`ContainerRegistry|We are having trouble connecting to the Container Registry. Please try refreshing the page. If this error persists, please review %{docLinkStart}the troubleshooting documentation%{docLinkEnd}.`,

View File

@ -36,6 +36,7 @@ export default () => {
isGroupPage,
isAdmin,
showCleanupPolicyLink,
showContainerRegistrySettings,
showUnfinishedTagCleanupCallout,
connectionError,
invalidPathError,
@ -69,6 +70,7 @@ export default () => {
isGroupPage: parseBoolean(isGroupPage),
isAdmin: parseBoolean(isAdmin),
showCleanupPolicyLink: parseBoolean(showCleanupPolicyLink),
showContainerRegistrySettings: parseBoolean(showContainerRegistrySettings),
showUnfinishedTagCleanupCallout: parseBoolean(showUnfinishedTagCleanupCallout),
connectionError: parseBoolean(connectionError),
invalidPathError: parseBoolean(invalidPathError),

View File

@ -1,5 +1,6 @@
<script>
import {
GlButton,
GlEmptyState,
GlTooltipDirective,
GlSprintf,
@ -28,12 +29,14 @@ import {
GRAPHQL_PAGE_SIZE,
FETCH_IMAGES_LIST_ERROR_MESSAGE,
SORT_FIELDS,
SETTINGS_TEXT,
} from '../constants/index';
import getContainerRepositoriesDetails from '../graphql/queries/get_container_repositories_details.query.graphql';
export default {
name: 'RegistryListPage',
components: {
GlButton,
GlEmptyState,
ProjectEmptyState: () =>
import(
@ -75,6 +78,7 @@ export default {
CONNECTION_ERROR_MESSAGE,
EMPTY_RESULT_TITLE,
EMPTY_RESULT_MESSAGE,
SETTINGS_TEXT,
},
searchConfig: SORT_FIELDS,
apollo: {
@ -306,6 +310,13 @@ export default {
:docker-push-command="dockerPushCommand"
:docker-login-command="dockerLoginCommand"
/>
<gl-button
v-if="config.showContainerRegistrySettings"
v-gl-tooltip="$options.i18n.SETTINGS_TEXT"
icon="settings"
:href="config.cleanupPoliciesSettingsPath"
:aria-label="$options.i18n.SETTINGS_TEXT"
/>
</template>
</registry-header>
<persisted-search

View File

@ -33,6 +33,11 @@ export default {
default: false,
},
},
data() {
return {
isUserAvatarListExpanded: false,
};
},
computed: {
approvers() {
return this.approvalState.approvedBy?.nodes || [];
@ -120,6 +125,14 @@ export default {
return gon.current_user_id;
},
},
methods: {
onUserAvatarListExpanded() {
this.isUserAvatarListExpanded = true;
},
onUserAvatarListCollapsed() {
this.isUserAvatarListExpanded = false;
},
},
};
</script>
@ -130,9 +143,12 @@ export default {
<span v-if="approvalLeftMessage">{{ message }}</span>
<span v-else class="gl-font-weight-bold">{{ message }}</span>
<user-avatar-list
class="gl-display-inline-flex gl-vertical-align-middle"
class="gl-display-inline-block"
:class="{ 'gl-pt-1': isUserAvatarListExpanded }"
:img-size="24"
:items="approvers"
@expanded="onUserAvatarListExpanded"
@collapsed="onUserAvatarListCollapsed"
/>
</template>
<template v-if="disableCommittersApproval && currentUserHasCommitted">

View File

@ -60,9 +60,11 @@ export default {
methods: {
expand() {
this.isExpanded = true;
this.$emit('expanded');
},
collapse() {
this.isExpanded = false;
this.$emit('collapsed');
},
},
};

View File

@ -12,6 +12,10 @@ class DashboardController < Dashboard::ApplicationController
before_action :set_show_full_reference, only: [:issues, :merge_requests]
before_action :check_filters_presence!, only: [:issues, :merge_requests]
before_action only: :merge_requests do
push_frontend_feature_flag(:mr_approved_filter, type: :ops)
end
respond_to :html
feature_category :user_profile, [:activity]

View File

@ -38,6 +38,10 @@ class GroupsController < Groups::ApplicationController
push_force_frontend_feature_flag(:work_items, group.work_items_feature_flag_enabled?)
end
before_action only: :merge_requests do
push_frontend_feature_flag(:mr_approved_filter, type: :ops)
end
helper_method :captcha_required?
skip_cross_project_access_check :index, :new, :create, :edit, :update, :destroy, :projects

View File

@ -32,6 +32,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
before_action only: :index do
push_frontend_feature_flag(:mr_approved_filter, type: :ops)
end
before_action only: [:show, :diffs] do
push_frontend_feature_flag(:content_editor_on_issues, project&.group)
push_force_frontend_feature_flag(:content_editor_on_issues, project&.content_editor_on_issues_feature_flag_enabled?)

View File

@ -187,7 +187,7 @@ class MergeRequestsFinder < IssuableFinder
def by_approved(items)
approved_param = Gitlab::Utils.to_boolean(params.fetch(:approved, nil))
return items if approved_param.nil?
return items if approved_param.nil? || Feature.disabled?(:mr_approved_filter, type: :ops)
if approved_param
items.with_approvals

View File

@ -57,7 +57,10 @@ module Resolvers
argument :approved, GraphQL::Types::Boolean,
required: false,
description: 'Limit results to approved merge requests.'
description: <<~DESC
Limit results to approved merge requests.
Available only when the feature flag `mr_approved_filter` is enabled.
DESC
argument :created_after, Types::TimeType,
required: false,

View File

@ -4,7 +4,7 @@
.row.justify-content-center
.col-md-5.new-session-forms-container
.login-page
#signin-container{ class: "#{'borderless' if Feature.enabled?(:restyle_login_page, @project)}" }
#signin-container{ class: ('borderless' if Feature.enabled?(:restyle_login_page, @project)) }
- if any_form_based_providers_enabled?
= render 'devise/shared/tabs_ldap', show_password_form: allow_admin_mode_password_authentication_for_web?, render_signup_link: false
- else

View File

@ -4,7 +4,7 @@
.row.justify-content-center
.col-md-5.new-session-forms-container
.login-page
#signin-container
#signin-container{ class: ('borderless' if Feature.enabled?(:restyle_login_page, @project)) }
= render 'devise/shared/tab_single', tab_title: _('Enter Admin Mode')
.tab-content
.login-box.tab-pane.gl-p-5.active{ id: 'login-pane', role: 'tabpanel' }

View File

@ -6,15 +6,29 @@
- notification_email = @current_user.present? ? @current_user.notification_email_or_default : nil
.nav-controls.issues-nav-controls.gl-font-size-0
- if show_feed_buttons
= render 'shared/issuable/feed_buttons'
.js-csv-import-export-buttons{ data: { show_export_button: show_export_button.to_s, show_import_button: show_import_button.to_s, issuable_type: issuable_type, issuable_count: issuables_count_for_state(issuable_type.to_sym, params[:state]), email: notification_email, export_csv_path: export_csv_project_issues_path(@project, request.query_parameters), import_csv_issues_path: import_csv_namespace_project_issues_path, container_class: 'gl-mr-3', can_edit: can_edit.to_s, project_import_jira_path: project_import_jira_path(@project), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) } }
- if @can_bulk_update
= button_tag _("Edit issues"), class: "gl-button btn btn-default gl-mr-3 js-bulk-update-toggle"
- if show_new_issue_link?(@project)
= link_to _("New issue"), new_project_issue_path(@project,
issue: { milestone_id: finder.milestones.first.try(:id) }),
class: "gl-button btn btn-confirm",
class: "gl-button btn btn-confirm gl-mr-3",
id: "new_issue_link"
.dropdown.gl-dropdown
= button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md gl-button gl-dropdown gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret has-tooltip gl-display-none! gl-md-display-inline-flex!", data: { toggle: 'dropdown', title: _('Actions') } do
= sprite_icon "ellipsis_v", size: 16, css_class: "dropdown-icon gl-icon"
%span.gl-sr-only
= _('Actions')
= button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md btn-block gl-button gl-dropdown-toggle gl-md-display-none!", data: { 'toggle' => 'dropdown' } do
%span.gl-dropdown-button-text= _('Actions')
= sprite_icon "chevron-down", size: 16, css_class: "dropdown-icon gl-icon"
.dropdown-menu.dropdown-menu-right
.gl-dropdown-inner
.gl-dropdown-contents
%ul
.js-csv-import-export-buttons{ data: { show_export_button: show_export_button.to_s, show_import_button: show_import_button.to_s, issuable_type: issuable_type, issuable_count: issuables_count_for_state(issuable_type.to_sym, params[:state]), email: notification_email, export_csv_path: export_csv_project_issues_path(@project, request.query_parameters), import_csv_issues_path: import_csv_namespace_project_issues_path, can_edit: can_edit.to_s, project_import_jira_path: project_import_jira_path(@project), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) } }
%li.gl-dropdown-divider
%hr.dropdown-divider
%li.gl-dropdown-item
- if show_feed_buttons
= render 'shared/issuable/feed_buttons'

View File

@ -1,12 +1,27 @@
- issuable_type = 'merge_request'
- notification_email = @current_user.present? ? @current_user.notification_email_or_default : nil
= render 'shared/issuable/feed_buttons', show_calendar_button: false
.js-csv-import-export-buttons{ data: { show_export_button: "true", issuable_type: issuable_type, issuable_count: issuables_count_for_state(issuable_type.to_sym, params[:state]), email: notification_email, export_csv_path: export_csv_project_merge_requests_path(@project, request.query_parameters), container_class: 'gl-mr-3' } }
- if @can_bulk_update
= render Pajamas::ButtonComponent.new(type: :submit, button_options: { class: 'gl-mr-3 js-bulk-update-toggle' }) do
= _("Edit merge requests")
- if merge_project
= render Pajamas::ButtonComponent.new(href: new_merge_request_path, variant: :confirm) do
= _("New merge request")
.dropdown.gl-dropdown
= button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md gl-button gl-dropdown gl-dropdown-toggle btn-default-tertiary dropdown-icon-only dropdown-toggle-no-caret has-tooltip gl-display-none! gl-md-display-inline-flex!", data: { toggle: 'dropdown', title: _('Actions') } do
= sprite_icon "ellipsis_v", size: 16, css_class: "dropdown-icon gl-icon"
%span.gl-sr-only
= _('Actions')
= button_tag type: 'button', class: "btn dropdown-toggle btn-default btn-md btn-block gl-button gl-dropdown-toggle gl-md-display-none!", data: { 'toggle' => 'dropdown' } do
%span.gl-dropdown-button-text= _('Actions')
= sprite_icon "chevron-down", size: 16, css_class: "dropdown-icon gl-icon"
.dropdown-menu.dropdown-menu-right
.gl-dropdown-inner
.gl-dropdown-contents
%ul
.js-csv-import-export-buttons{ data: { show_export_button: "true", issuable_type: issuable_type, issuable_count: issuables_count_for_state(issuable_type.to_sym, params[:state]), email: notification_email, export_csv_path: export_csv_project_merge_requests_path(@project, request.query_parameters) } }
%li.gl-dropdown-divider
%hr.dropdown-divider
%li.gl-dropdown-item
= render 'shared/issuable/feed_buttons', show_calendar_button: false

View File

@ -18,6 +18,7 @@
"gid_prefix": container_repository_gid_prefix,
"is_admin": current_user&.admin.to_s,
"show_cleanup_policy_link": show_cleanup_policy_link(@project).to_s,
"show_container_registry_settings": show_container_registry_settings(@project).to_s,
"cleanup_policies_settings_path": project_settings_packages_and_registries_path(@project),
connection_error: (!!@connection_error).to_s,
invalid_path_error: (!!@invalid_path_error).to_s,

View File

@ -42,7 +42,7 @@
= link_to _('New issue'), button_path, class: 'gl-button btn btn-confirm', id: 'new_issue_link'
- if show_import_button
.js-csv-import-export-buttons{ data: { show_import_button: 'true', issuable_type: 'issue', import_csv_issues_path: import_csv_namespace_project_issues_path, can_edit: can_edit.to_s, project_import_jira_path: project_import_jira_path(@project), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), container_class: 'gl-w-full gl-sm-w-auto gl-sm-mr-3 gl-display-inline-flex gl-vertical-align-middle', show_label: 'true' } }
.js-csv-import-export-buttons{ data: { show_import_button: 'true', issuable_type: 'issue', import_csv_issues_path: import_csv_namespace_project_issues_path, can_edit: can_edit.to_s, project_import_jira_path: project_import_jira_path(@project), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) } }
%hr
%p.gl-text-center.gl-mb-0
%strong

View File

@ -1,8 +1,10 @@
- show_calendar_button = local_assigns.fetch(:show_calendar_button, true)
= render Pajamas::ButtonComponent.new(href: safe_params.merge(rss_url_options), button_options: { class: 'has-tooltip btn-icon', title: _('Subscribe to RSS feed'), 'aria-label': _('Subscribe to RSS feed'), data: { container: 'body', testid: 'rss-feed-link' } }) do
= sprite_icon('rss')
- if show_calendar_button
= render Pajamas::ButtonComponent.new(href: safe_params.merge(calendar_url_options), button_options: { class: 'has-tooltip btn-icon', title: _('Subscribe to calendar'), 'aria-label': _('Subscribe to calendar'), data: { container: 'body' } }) do
= sprite_icon('calendar')
= link_to safe_params.merge(calendar_url_options), class: 'dropdown-item' do
.gl-dropdown-item-text-wrapper
= _("Subscribe to calendar")
= link_to safe_params.merge(rss_url_options), class: 'dropdown-item' do
.gl-dropdown-item-text-wrapper
= _("Subscribe to RSS feed")

View File

@ -162,14 +162,15 @@
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.gl-button.btn.btn-link{ type: 'button' }
= _('No')
#js-dropdown-approved.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
%button.gl-button.btn.btn-link{ type: 'button' }
= _('Yes')
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.gl-button.btn.btn-link{ type: 'button' }
= _('No')
- if ::Feature.enabled?(:mr_approved_filter, type: :ops)
#js-dropdown-approved.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
%button.gl-button.btn.btn-link{ type: 'button' }
= _('Yes')
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.gl-button.btn.btn-link{ type: 'button' }
= _('No')
#js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }

View File

@ -1,8 +1,8 @@
---
name: refactor_security_extension
introduced_by_url:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84896
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365320
milestone: '14.10'
type: development
group: group::threat insights
default_enabled: false
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: mr_approved_filter
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118087
rollout_issue_url:
milestone: '16.0'
type: ops
group: group::code review
default_enabled: false

View File

@ -15240,7 +15240,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="groupmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="groupmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="groupmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="groupmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="groupmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
@ -16572,7 +16572,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestassigneeassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestassigneeassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestassigneeassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestassigneeassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="mergerequestassigneeassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -16607,7 +16607,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestassigneeauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestassigneeauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestassigneeauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestassigneeauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="mergerequestassigneeauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -16659,7 +16659,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestassigneereviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestassigneereviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestassigneereviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestassigneereviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestassigneereviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
@ -16822,7 +16822,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestauthorassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestauthorassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestauthorassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestauthorassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="mergerequestauthorassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -16857,7 +16857,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestauthorauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestauthorauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestauthorauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestauthorauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="mergerequestauthorauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -16909,7 +16909,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestauthorreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestauthorreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestauthorreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestauthorreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestauthorreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
@ -17091,7 +17091,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestparticipantassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestparticipantassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestparticipantassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestparticipantassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="mergerequestparticipantassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -17126,7 +17126,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestparticipantauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestparticipantauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestparticipantauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestparticipantauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="mergerequestparticipantauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -17178,7 +17178,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestparticipantreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestparticipantreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestparticipantreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestparticipantreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestparticipantreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
@ -17360,7 +17360,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestreviewerassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestreviewerassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestreviewerassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestreviewerassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="mergerequestreviewerassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -17395,7 +17395,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestreviewerauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestreviewerauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestreviewerauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestreviewerauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="mergerequestreviewerauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -17447,7 +17447,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mergerequestreviewerreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="mergerequestreviewerreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="mergerequestreviewerreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="mergerequestreviewerreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="mergerequestreviewerreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
@ -19445,7 +19445,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="projectmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="projectmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="projectmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="projectmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
@ -21757,7 +21757,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="usercoreassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="usercoreassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="usercoreassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="usercoreassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="usercoreassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -21792,7 +21792,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="usercoreauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="usercoreauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="usercoreauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="usercoreauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="usercoreauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -21844,7 +21844,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="usercorereviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="usercorereviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="usercorereviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="usercorereviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="usercorereviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
@ -26440,7 +26440,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="userassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="userassignedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="userassignedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="userassignedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="userassignedmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -26475,7 +26475,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="userauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="userauthoredmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="userauthoredmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="userauthoredmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |
| <a id="userauthoredmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. |
@ -26527,7 +26527,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="userreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. |
| <a id="userreviewrequestedmergerequestsapproved"></a>`approved` | [`Boolean`](#boolean) | Limit results to approved merge requests. Available only when the feature flag `mr_approved_filter` is enabled. |
| <a id="userreviewrequestedmergerequestsassigneeusername"></a>`assigneeUsername` | [`String`](#string) | Username of the assignee. |
| <a id="userreviewrequestedmergerequestsauthorusername"></a>`authorUsername` | [`String`](#string) | Username of the author. |
| <a id="userreviewrequestedmergerequestscreatedafter"></a>`createdAfter` | [`Time`](#time) | Merge requests created after this timestamp. |

View File

@ -46,7 +46,7 @@ Supported attributes:
| ------------------------------- | -------------- | -------- | ----------- |
| `approved_by_ids` **(PREMIUM)** | integer array | **{dotted-circle}** No | Returns merge requests which have been approved by all the users with the given `id`. Maximum of 5. `None` returns merge requests with no approvals. `Any` returns merge requests with an approval. |
| `approver_ids` **(PREMIUM)** | integer array | **{dotted-circle}** No | Returns merge requests which have specified all the users with the given `id` as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
| `approved` | string | **{dotted-circle}** No | Filters merge requests by their `approved` status. `yes` returns only approved merge requests. `no` returns only non-approved merge requests. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3159) in GitLab 15.11. |
| `approved` | string | **{dotted-circle}** No | Filters merge requests by their `approved` status. `yes` returns only approved merge requests. `no` returns only non-approved merge requests. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3159) in GitLab 15.11. Available only when the feature flag `mr_approved_filter` is enabled. |
| `assignee_id` | integer | **{dotted-circle}** No | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. |
| `author_id` | integer | **{dotted-circle}** No | Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. Combine with `scope=all` or `scope=assigned_to_me`. |
| `author_username` | string | **{dotted-circle}** No | Returns merge requests created by the given `username`. Mutually exclusive with `author_id`. |
@ -250,7 +250,7 @@ Supported attributes:
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
| `approved_by_ids` **(PREMIUM)** | integer array | **{dotted-circle}** No | Returns merge requests which have been approved by all the users with the given `id`, with a maximum of 5. `None` returns merge requests with no approvals. `Any` returns merge requests with an approval. |
| `approver_ids` **(PREMIUM)** | integer array | **{dotted-circle}** No | Returns merge requests which have specified all the users with the given `id` as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
| `approved` | string | **{dotted-circle}** No | Filters merge requests by their `approved` status. `yes` returns only approved merge requests. `no` returns only non-approved merge requests. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3159) in GitLab 15.11. |
| `approved` | string | **{dotted-circle}** No | Filters merge requests by their `approved` status. `yes` returns only approved merge requests. `no` returns only non-approved merge requests. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3159) in GitLab 15.11. Available only when the feature flag `mr_approved_filter` is enabled. |
| `assignee_id` | integer | **{dotted-circle}** No | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. |
| `author_id` | integer | **{dotted-circle}** No | Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. |
| `author_username` | string | **{dotted-circle}** No | Returns merge requests created by the given `username`. Mutually exclusive with `author_id`. |
@ -441,7 +441,7 @@ Supported attributes:
| `approved_by_ids` **(PREMIUM)** | integer array | **{dotted-circle}** No | Returns merge requests which have been approved by all the users with the given `id`, with a maximum of 5. `None` returns merge requests with no approvals. `Any` returns merge requests with an approval. |
| `approved_by_usernames` **(PREMIUM)** | string array | **{dotted-circle}** No | Returns merge requests which have been approved by all the users with the given `username`, with a maximum of 5. `None` returns merge requests with no approvals. `Any` returns merge requests with an approval. |
| `approver_ids` **(PREMIUM)** | integer array | **{dotted-circle}** No | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
| `approved` | string | **{dotted-circle}** No | Filters merge requests by their `approved` status. `yes` returns only approved merge requests. `no` returns only non-approved merge requests. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3159) in GitLab 15.11. |
| `approved` | string | **{dotted-circle}** No | Filters merge requests by their `approved` status. `yes` returns only approved merge requests. `no` returns only non-approved merge requests. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3159) in GitLab 15.11. Available only when the feature flag `mr_approved_filter` is enabled. |
| `assignee_id` | integer | **{dotted-circle}** No | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. |
| `author_id` | integer | **{dotted-circle}** No | Returns merge requests created by the given user `id`. Mutually exclusive with `author_username`. |
| `author_username` | string | **{dotted-circle}** No | Returns merge requests created by the given `username`. Mutually exclusive with `author_id`. |

View File

@ -146,7 +146,7 @@ self-managed GitLab instances with Jira Cloud, you can do one of the following:
[Prerequisites](#prerequisites-1)
To configure your Atlassian Cloud instance so you can install applications
To configure your Jira instance so you can install applications
from outside the Marketplace:
1. Sign in to your Jira instance as an administrator.

View File

@ -59,7 +59,7 @@ Importing large projects may take several minutes depending on the size of the i
To import Jira issues to a GitLab project:
1. On the **{issues}** **Issues** page, select **Import Issues** (**{import}**) **> Import from Jira**.
1. On the **{issues}** **Issues** page, select **Actions** (**{ellipsis_v}**) **> Import from Jira**.
![Import issues from Jira button](img/jira/import_issues_from_jira_button_v12_10.png)

View File

@ -36,7 +36,7 @@ To import issues:
1. Go to your project's Issues list page.
1. Open the import feature, depending if the project has issues:
- The project has existing issues: in the upper-right corner, next to **Edit issues**, select the import icon (**{import}**).
- The project has existing issues: in the upper-right corner, next to **Edit issues**, select **Actions** (**{ellipsis_v}**) **> Import CSV**.
- The project has no issues: in the middle of the page, select **Import CSV**.
1. Select the file you want to import, and then select **Import issues**.

View File

@ -49,7 +49,8 @@ server's time zone.
Issues with due dates can also be exported as an iCalendar feed. The URL of the
feed can be added to calendar applications. The feed is accessible by selecting
the **Subscribe to calendar** button on the following pages:
the **Subscribe to calendar** option in the **Actions** (**{ellipsis_v}**) dropdown
list on the following pages:
- The **Assigned Issues** page linked on the right side of the GitLab header
- The **Project Issues** page

View File

@ -16,7 +16,7 @@ To export merge requests to a CSV file:
1. On the left sidebar, select **Merge requests** .
1. Add any searches or filters. This can help you keep the size of the CSV file under the 15 MB limit. The limit ensures
the file can be emailed to a variety of email providers.
1. Select **Export as CSV** (**{export}**).
1. Select **Actions** (**{ellipsis_v}**) **> Export as CSV**.
1. Confirm the correct number of merge requests are to be exported.
1. Select **Export merge requests**.

View File

@ -11478,6 +11478,9 @@ msgstr ""
msgid "ContainerRegistry|Configuration digest: %{digest}"
msgstr ""
msgid "ContainerRegistry|Configure in settings"
msgstr ""
msgid "ContainerRegistry|Container Registry"
msgstr ""
@ -38210,9 +38213,6 @@ msgstr ""
msgid "Runners|Clear selection"
msgstr ""
msgid "Runners|Cloud templates"
msgstr ""
msgid "Runners|Command to register runner"
msgstr ""
@ -52109,6 +52109,9 @@ msgstr ""
msgid "ciReport|Container Scanning"
msgstr ""
msgid "ciReport|Container Scanning detects known vulnerabilities in your container images."
msgstr ""
msgid "ciReport|Container scanning"
msgstr ""
@ -52139,7 +52142,7 @@ msgstr ""
msgid "ciReport|Dependency Scanning"
msgstr ""
msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
msgid "ciReport|Dependency Scanning detects known vulnerabilities in your project's dependencies."
msgstr ""
msgid "ciReport|Dependency scanning"
@ -52166,7 +52169,7 @@ msgstr ""
msgid "ciReport|Dynamic Application Security Testing (DAST)"
msgstr ""
msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
msgid "ciReport|Dynamic Application Security Testing (DAST) detects vulnerabilities in your web application."
msgstr ""
msgid "ciReport|Failed to load %{reportName} report"
@ -52261,7 +52264,7 @@ msgstr ""
msgid "ciReport|Secret Detection"
msgstr ""
msgid "ciReport|Secret Detection detects secrets and credentials vulnerabilities in your source code."
msgid "ciReport|Secret Detection detects leaked credentials in your source code."
msgstr ""
msgid "ciReport|Secret detection"
@ -52294,7 +52297,7 @@ msgstr ""
msgid "ciReport|Static Application Security Testing (SAST)"
msgstr ""
msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
msgid "ciReport|Static Application Security Testing (SAST) detects potential vulnerabilities in your source code."
msgstr ""
msgid "ciReport|TTFB P90"

View File

@ -22,6 +22,13 @@ module QA
view 'app/assets/javascripts/issuable/components/csv_import_export_buttons.vue' do
element :export_as_csv_button
element :import_from_jira_link
end
view 'app/assets/javascripts/issues/list/components/issues_list_app.vue' do
element :issues_list_more_actions_dropdown
end
view 'app/assets/javascripts/issues/list/components/empty_state_without_any_issues.vue' do
element :import_issues_dropdown
end
@ -59,6 +66,10 @@ module QA
click_element(:import_issues_dropdown)
end
def click_issues_list_more_actions_dropdown
click_element(:issues_list_more_actions_dropdown)
end
def export_issues_modal
find_element(:export_issuable_modal)
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage', product_group: :import,
RSpec.describe 'Manage', product_group: :import_and_integrate,
quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/407297',
type: :investigating

View File

@ -7,7 +7,7 @@ require "etc"
# rubocop:disable Rails/Pluck
module QA
RSpec.describe 'Manage', :github, requires_admin: 'creates users', only: { job: 'large-github-import' } do
describe 'Project import', product_group: :import do # rubocop:disable RSpec/MultipleMemoizedHelpers
describe 'Project import', product_group: :import_and_integrate do # rubocop:disable RSpec/MultipleMemoizedHelpers
let(:github_repo) { ENV['QA_LARGE_IMPORT_REPO'] || 'rspec/rspec-core' }
let(:import_max_duration) { ENV['QA_LARGE_IMPORT_DURATION']&.to_i || 7200 }
let(:logger) { Runtime::Logger.logger }

View File

@ -7,7 +7,7 @@ module QA
:requires_admin,
:integrations,
:orchestrated,
product_group: :integrations
product_group: :import_and_integrate
) do
before(:context) do
toggle_local_requests(true)

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe "Manage", :reliable, product_group: :import do
RSpec.describe "Manage", :reliable, product_group: :import_and_integrate do
include_context "with gitlab group migration"
describe "Gitlab migration" do

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Gitlab migration', product_group: :import do
describe 'Gitlab migration', product_group: :import_and_integrate do
include_context 'with gitlab project migration'
let!(:source_issue) do

View File

@ -5,7 +5,7 @@
# rubocop:disable Rails/Pluck, Layout/LineLength, RSpec/MultipleMemoizedHelpers
module QA
RSpec.describe "Manage", :skip_live_env, only: { job: "large-gitlab-import" } do
describe "Gitlab migration", orchestrated: false, product_group: :import do
describe "Gitlab migration", orchestrated: false, product_group: :import_and_integrate do
include_context "with gitlab group migration"
let!(:logger) { Runtime::Logger.logger }

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Gitlab migration', product_group: :import do
describe 'Gitlab migration', product_group: :import_and_integrate do
include_context 'with gitlab project migration'
let!(:source_member) do

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Gitlab migration', product_group: :import do
describe 'Gitlab migration', product_group: :import_and_integrate do
include_context 'with gitlab project migration'
let!(:source_project_with_readme) { true }

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Gitlab migration', product_group: :import do
describe 'Gitlab migration', product_group: :import_and_integrate do
include_context 'with gitlab project migration'
context 'with ci pipeline' do

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Gitlab migration', product_group: :import do
describe 'Gitlab migration', product_group: :import_and_integrate do
include_context 'with gitlab project migration'
# this spec is used as a sanity test for gitlab migration because it can run outside of orchestrated setup

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Gitlab migration', product_group: :import do
describe 'Gitlab migration', product_group: :import_and_integrate do
include_context 'with gitlab project migration'
context 'with release' do

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage', :requires_admin, :skip_live_env, except: { job: 'review-qa-*' } do
describe 'rate limits', :reliable, product_group: :integrations do
describe 'rate limits', :reliable, product_group: :import_and_integrate do
let(:rate_limited_user) { Resource::User.fabricate_via_api! }
let(:api_client) { Runtime::API::Client.new(:gitlab, user: rate_limited_user) }
let!(:request) { Runtime::API::Request.new(api_client, '/users') }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage', product_group: :import do
RSpec.describe 'Manage', product_group: :import_and_integrate do
describe 'GitHub import' do
include_context 'with github import'

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage', :requires_admin, :skip_live_env, except: { job: 'review-qa-*' } do
describe 'Jenkins integration', product_group: :integrations do
describe 'Jenkins integration', product_group: :import_and_integrate do
let(:jenkins_server) { Service::DockerRun::Jenkins.new }
let(:jenkins_client) do

View File

@ -4,7 +4,7 @@ module QA
RSpec.describe 'Manage' do
include Support::API
describe 'Jira integration', :jira, :orchestrated, :requires_admin, product_group: :integrations do
describe 'Jira integration', :jira, :orchestrated, :requires_admin, product_group: :import_and_integrate do
let(:jira_project_key) { 'JITP' }
let(:project) do
Resource::Project.fabricate_via_api! do |project|

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Manage', :reliable do
describe 'Jira issue import', :jira, :orchestrated, :requires_admin, product_group: :integrations do
describe 'Jira issue import', :jira, :orchestrated, :requires_admin, product_group: :import_and_integrate do
let(:jira_project_key) { "JITD" }
let(:jira_issue_title) { "[#{jira_project_key}-1] Jira to GitLab Test Issue" }
let(:jira_issue_description) { "This issue is for testing importing Jira issues to GitLab." }

View File

@ -24,7 +24,7 @@ module QA
end
end
RSpec.describe 'Manage', :orchestrated, :runner, :requires_admin, :smtp, product_group: :integrations do
RSpec.describe 'Manage', :orchestrated, :runner, :requires_admin, :smtp, product_group: :import_and_integrate do
describe 'Pipeline status emails' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
let(:emails) { %w[foo@bar.com baz@buzz.com] }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
describe 'Manage', :reliable, product_group: :import do
describe 'Manage', :reliable, product_group: :import_and_integrate do
describe 'Gitlab migration' do
include_context "with gitlab group migration"

View File

@ -24,6 +24,8 @@ module QA
it 'successfully exports issues list as CSV', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347968' do
Page::Project::Issue::Index.perform do |index|
index.click_issues_list_more_actions_dropdown
index.click_export_as_csv_button
expect(index.export_issues_modal).to have_content('2 issues selected')

View File

@ -229,7 +229,7 @@ postgresql:
memory: 1000Mi
limits:
cpu: 1000m
memory: 1600Mi
memory: 1800Mi
master:
nodeSelector:
preemptible: "false"

View File

@ -23,6 +23,10 @@ RSpec.describe 'Group issues page', feature_category: :subgroups do
context 'rss feed' do
let(:access_level) { ProjectFeature::ENABLED }
before do
click_button 'Actions'
end
context 'when signed in' do
let(:user) do
user_in_group.ensure_feed_token

View File

@ -16,6 +16,7 @@ RSpec.describe 'Issues csv', :js, feature_category: :team_planning do
def request_csv(params = {})
visit project_issues_path(project, params)
click_button 'Actions'
click_button 'Export as CSV'
click_on 'Export issues'
end

View File

@ -23,6 +23,7 @@ RSpec.describe 'Project Issues RSS', :js, feature_category: :team_planning do
before do
sign_in(user)
visit path
click_button 'Actions'
end
it_behaves_like "it has an RSS link with current_user's feed token"
@ -32,6 +33,7 @@ RSpec.describe 'Project Issues RSS', :js, feature_category: :team_planning do
context 'when signed out' do
before do
visit path
click_button 'Actions'
end
it_behaves_like "it has an RSS link without a feed token"

View File

@ -25,7 +25,7 @@ RSpec.describe 'Project Merge Requests RSS', feature_category: :code_review_work
visit path
end
it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "it has an RSS link with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
@ -34,7 +34,7 @@ RSpec.describe 'Project Merge Requests RSS', feature_category: :code_review_work
visit path
end
it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "it has an RSS link without a feed token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
end

View File

@ -14,6 +14,7 @@ RSpec.describe 'Merge Requests > Exports as CSV', :js, feature_category: :code_r
context 'button is clicked' do
before do
click_button 'Actions'
click_button 'Export as CSV'
end

View File

@ -30,6 +30,20 @@ RSpec.describe 'Container Registry', :js, feature_category: :projects do
expect(page).to have_title _('Container Registry')
end
it 'does not have link to settings' do
visit_container_registry
expect(page).not_to have_link _('Configure in settings')
end
it 'has link to settings when user is maintainer' do
project.add_maintainer(user)
visit_container_registry
expect(page).to have_link _('Configure in settings')
end
context 'when there are no image repositories' do
it 'list page has no container title' do
visit_container_registry

View File

@ -498,16 +498,40 @@ RSpec.describe MergeRequestsFinder, feature_category: :code_review_workflow do
create(:approval, merge_request: merge_request3, user: user2)
end
it 'for approved' do
merge_requests = described_class.new(user, { approved: true }).execute
context 'when flag `mr_approved_filter` is disabled' do
before do
stub_feature_flags(mr_approved_filter: false)
end
expect(merge_requests).to contain_exactly(merge_request3)
it 'for approved' do
merge_requests = described_class.new(user, { approved: true }).execute
expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5)
end
it 'for not approved' do
merge_requests = described_class.new(user, { approved: false }).execute
expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5)
end
end
it 'for not approved' do
merge_requests = described_class.new(user, { approved: false }).execute
context 'when flag `mr_approved_filter` is enabled' do
before do
stub_feature_flags(mr_approved_filter: true)
end
expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request4, merge_request5)
it 'for approved' do
merge_requests = described_class.new(user, { approved: true }).execute
expect(merge_requests).to contain_exactly(merge_request3)
end
it 'for not approved' do
merge_requests = described_class.new(user, { approved: false }).execute
expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request4, merge_request5)
end
end
end

View File

@ -6,19 +6,12 @@ import {
LINUX_PLATFORM,
MACOS_PLATFORM,
WINDOWS_PLATFORM,
AWS_PLATFORM,
DOCKER_HELP_URL,
KUBERNETES_HELP_URL,
} from '~/ci/runner/constants';
import RunnerPlatformsRadioGroup from '~/ci/runner/components/runner_platforms_radio_group.vue';
const mockProvide = {
awsImgPath: 'awsLogo.svg',
dockerImgPath: 'dockerLogo.svg',
kubernetesImgPath: 'kubernetesLogo.svg',
};
describe('RunnerPlatformsRadioGroup', () => {
let wrapper;
@ -35,7 +28,6 @@ describe('RunnerPlatformsRadioGroup', () => {
value: null,
...props,
},
provide: mockProvide,
...options,
});
};
@ -51,7 +43,6 @@ describe('RunnerPlatformsRadioGroup', () => {
['Linux', null],
['macOS', null],
['Windows', null],
['AWS', expect.any(String)],
['Docker', expect.any(String)],
['Kubernetes', expect.any(String)],
]);
@ -69,7 +60,6 @@ describe('RunnerPlatformsRadioGroup', () => {
${'Linux'} | ${LINUX_PLATFORM}
${'macOS'} | ${MACOS_PLATFORM}
${'Windows'} | ${WINDOWS_PLATFORM}
${'AWS'} | ${AWS_PLATFORM}
`('user can select "$text"', async ({ text, value }) => {
const radio = findFormRadioByText(text);
expect(radio.props('value')).toBe(value);

View File

@ -1,5 +1,4 @@
import { GlButton, GlDropdown } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { createMockDirective } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CsvExportModal from '~/issuable/components/csv_export_modal.vue';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
@ -33,8 +32,7 @@ describe('CsvImportExportButtons', () => {
});
}
const findExportCsvButton = () => wrapper.findComponent(GlButton);
const findImportDropdown = () => wrapper.findComponent(GlDropdown);
const findExportCsvButton = () => wrapper.findByRole('menuitem', { name: 'Export as CSV' });
const findImportCsvButton = () => wrapper.findByRole('menuitem', { name: 'Import CSV' });
const findImportFromJiraLink = () => wrapper.findByRole('menuitem', { name: 'Import from Jira' });
const findExportCsvModal = () => wrapper.findComponent(CsvExportModal);
@ -50,13 +48,6 @@ describe('CsvImportExportButtons', () => {
expect(findExportCsvButton().exists()).toBe(true);
});
it('export button has a tooltip', () => {
const tooltip = getBinding(findExportCsvButton().element, 'gl-tooltip');
expect(tooltip).toBeDefined();
expect(tooltip.value).toBe('Export as CSV');
});
it('renders the export modal', () => {
expect(findExportCsvModal().props()).toMatchObject({ exportCsvPath, issuableCount });
});
@ -64,7 +55,7 @@ describe('CsvImportExportButtons', () => {
it('opens the export modal', () => {
findExportCsvButton().trigger('click');
expect(glModalDirective).toHaveBeenCalledWith(wrapper.vm.exportModalId);
expect(glModalDirective).toHaveBeenCalled();
});
});
@ -83,79 +74,38 @@ describe('CsvImportExportButtons', () => {
});
describe('when the showImportButton=true', () => {
beforeEach(() => {
wrapper = createComponent({ showImportButton: true });
});
it('displays the import dropdown', () => {
expect(findImportDropdown().exists()).toBe(true);
});
it('renders the import csv menu item', () => {
wrapper = createComponent({ showImportButton: true });
expect(findImportCsvButton().exists()).toBe(true);
});
describe('when showLabel=false', () => {
beforeEach(() => {
wrapper = createComponent({ showImportButton: true, showLabel: false });
});
it('hides button text', () => {
expect(findImportDropdown().props()).toMatchObject({
text: 'Import issues',
textSrOnly: true,
});
});
it('import button has a tooltip', () => {
const tooltip = getBinding(findImportDropdown().element, 'gl-tooltip');
expect(tooltip).toBeDefined();
expect(tooltip.value).toBe('Import issues');
});
});
describe('when showLabel=true', () => {
beforeEach(() => {
wrapper = createComponent({ showImportButton: true, showLabel: true });
});
it('displays a button text', () => {
expect(findImportDropdown().props()).toMatchObject({
text: 'Import issues',
textSrOnly: false,
});
});
it('import button has no tooltip', () => {
const tooltip = getBinding(findImportDropdown().element, 'gl-tooltip');
expect(tooltip.value).toBe(null);
});
});
it('renders the import modal', () => {
wrapper = createComponent({ showImportButton: true });
expect(findImportCsvModal().exists()).toBe(true);
});
it('opens the import modal', () => {
wrapper = createComponent({ showImportButton: true });
findImportCsvButton().trigger('click');
expect(glModalDirective).toHaveBeenCalledWith(wrapper.vm.importModalId);
expect(glModalDirective).toHaveBeenCalled();
});
describe('import from jira link', () => {
const projectImportJiraPath = 'gitlab-org/gitlab-test/-/import/jira';
beforeEach(() => {
wrapper = createComponent({
showImportButton: true,
canEdit: true,
projectImportJiraPath,
});
});
describe('when canEdit=true', () => {
beforeEach(() => {
wrapper = createComponent({
showImportButton: true,
canEdit: true,
projectImportJiraPath,
});
});
it('renders the import dropdown item', () => {
expect(findImportFromJiraLink().exists()).toBe(true);
});
@ -182,8 +132,8 @@ describe('CsvImportExportButtons', () => {
wrapper = createComponent({ showImportButton: false });
});
it('does not display the import dropdown', () => {
expect(findImportDropdown().exists()).toBe(false);
it('does not render the import csv menu item', () => {
expect(findImportCsvButton().exists()).toBe(false);
});
it('does not render the import modal', () => {

View File

@ -1,4 +1,4 @@
import { GlEmptyState, GlLink } from '@gitlab/ui';
import { GlDropdown, GlEmptyState, GlLink } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_any_issues.vue';
@ -26,6 +26,7 @@ describe('EmptyStateWithoutAnyIssues component', () => {
};
const findCsvImportExportButtons = () => wrapper.findComponent(CsvImportExportButtons);
const findCsvImportExportDropdown = () => wrapper.findComponent(GlDropdown);
const findGlEmptyState = () => wrapper.findComponent(GlEmptyState);
const findGlLink = () => wrapper.findComponent(GlLink);
const findIssuesHelpPageLink = () =>
@ -135,6 +136,7 @@ describe('EmptyStateWithoutAnyIssues component', () => {
it('renders', () => {
mountComponent({ props: { showCsvButtons: true } });
expect(findCsvImportExportDropdown().props('text')).toBe('Import issues');
expect(findCsvImportExportButtons().props()).toMatchObject({
exportCsvPath: defaultProps.exportCsvPathWithQuery,
issuableCount: 0,
@ -146,6 +148,7 @@ describe('EmptyStateWithoutAnyIssues component', () => {
it('does not render', () => {
mountComponent({ props: { showCsvButtons: false } });
expect(findCsvImportExportDropdown().exists()).toBe(false);
expect(findCsvImportExportButtons().exists()).toBe(false);
});
});

View File

@ -1,4 +1,4 @@
import { GlButton } from '@gitlab/ui';
import { GlButton, GlDropdown } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
@ -11,6 +11,7 @@ import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_coun
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import {
getIssuesCountsQueryResponse,
@ -126,12 +127,16 @@ describe('CE IssuesListApp component', () => {
const mockIssuesQueryResponse = jest.fn().mockResolvedValue(defaultQueryResponse);
const mockIssuesCountsQueryResponse = jest.fn().mockResolvedValue(getIssuesCountsQueryResponse);
const findCalendarButton = () =>
wrapper.findByRole('menuitem', { name: IssuesListApp.i18n.calendarLabel });
const findCsvImportExportButtons = () => wrapper.findComponent(CsvImportExportButtons);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findIssuableByEmail = () => wrapper.findComponent(IssuableByEmail);
const findGlButton = () => wrapper.findComponent(GlButton);
const findGlButtons = () => wrapper.findAllComponents(GlButton);
const findGlButtonAt = (index) => findGlButtons().at(index);
const findIssuableList = () => wrapper.findComponent(IssuableList);
const findNewResourceDropdown = () => wrapper.findComponent(NewResourceDropdown);
const findRssButton = () => wrapper.findByRole('menuitem', { name: IssuesListApp.i18n.rssLabel });
const findLabelsToken = () =>
findIssuableList()
@ -232,63 +237,66 @@ describe('CE IssuesListApp component', () => {
});
describe('header action buttons', () => {
it('renders rss button', async () => {
wrapper = mountComponent({ mountFn: mount });
await waitForPromises();
describe('actions dropdown', () => {
it('renders', () => {
wrapper = mountComponent({ mountFn: mount });
expect(findGlButtonAt(0).props('icon')).toBe('rss');
expect(findGlButtonAt(0).attributes()).toMatchObject({
href: defaultProvide.rssPath,
'aria-label': IssuesListApp.i18n.rssLabel,
expect(findDropdown().props()).toMatchObject({
category: 'tertiary',
icon: 'ellipsis_v',
text: 'Actions',
textSrOnly: true,
});
});
});
it('renders calendar button', async () => {
wrapper = mountComponent({ mountFn: mount });
await waitForPromises();
describe('csv import/export buttons', () => {
describe('when user is signed in', () => {
beforeEach(() => {
setWindowLocation('?search=refactor&state=opened');
expect(findGlButtonAt(1).props('icon')).toBe('calendar');
expect(findGlButtonAt(1).attributes()).toMatchObject({
href: defaultProvide.calendarPath,
'aria-label': IssuesListApp.i18n.calendarLabel,
});
});
wrapper = mountComponent({
provide: { initialSortBy: CREATED_DESC, isSignedIn: true },
mountFn: mount,
});
describe('csv import/export component', () => {
describe('when user is signed in', () => {
beforeEach(() => {
setWindowLocation('?search=refactor&state=opened');
wrapper = mountComponent({
provide: { initialSortBy: CREATED_DESC, isSignedIn: true },
mountFn: mount,
return waitForPromises();
});
return waitForPromises();
it('renders', () => {
expect(findCsvImportExportButtons().props()).toMatchObject({
exportCsvPath: `${defaultProvide.exportCsvPath}?search=refactor&state=opened`,
issuableCount: 1,
});
});
});
it('renders', () => {
expect(findCsvImportExportButtons().props()).toMatchObject({
exportCsvPath: `${defaultProvide.exportCsvPath}?search=refactor&state=opened`,
issuableCount: 1,
describe('when user is not signed in', () => {
it('does not render', () => {
wrapper = mountComponent({ provide: { isSignedIn: false }, mountFn: mount });
expect(findCsvImportExportButtons().exists()).toBe(false);
});
});
describe('when in a group context', () => {
it('does not render', () => {
wrapper = mountComponent({ provide: { isProject: false }, mountFn: mount });
expect(findCsvImportExportButtons().exists()).toBe(false);
});
});
});
describe('when user is not signed in', () => {
it('does not render', () => {
wrapper = mountComponent({ provide: { isSignedIn: false }, mountFn: mount });
it('renders RSS button link', () => {
wrapper = mountComponent({ mountFn: mountExtended });
expect(findCsvImportExportButtons().exists()).toBe(false);
});
expect(findRssButton().attributes('href')).toBe(defaultProvide.rssPath);
});
describe('when in a group context', () => {
it('does not render', () => {
wrapper = mountComponent({ provide: { isProject: false }, mountFn: mount });
it('renders calendar button link', () => {
wrapper = mountComponent({ mountFn: mountExtended });
expect(findCsvImportExportButtons().exists()).toBe(false);
});
expect(findCalendarButton().attributes('href')).toBe(defaultProvide.calendarPath);
});
});
@ -296,7 +304,7 @@ describe('CE IssuesListApp component', () => {
it('renders when user has permissions', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: true }, mountFn: mount });
expect(findGlButtonAt(2).text()).toBe('Edit issues');
expect(findGlButton().text()).toBe('Edit issues');
});
it('does not render when user does not have permissions', () => {
@ -309,7 +317,7 @@ describe('CE IssuesListApp component', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: true }, mountFn: mount });
jest.spyOn(eventHub, '$emit');
findGlButtonAt(2).vm.$emit('click');
findGlButton().vm.$emit('click');
await waitForPromises();
expect(eventHub.$emit).toHaveBeenCalledWith('issuables:enableBulkEdit');
@ -320,8 +328,8 @@ describe('CE IssuesListApp component', () => {
it('renders when user has permissions', () => {
wrapper = mountComponent({ provide: { showNewIssueLink: true }, mountFn: mount });
expect(findGlButtonAt(2).text()).toBe('New issue');
expect(findGlButtonAt(2).attributes('href')).toBe(defaultProvide.newIssuePath);
expect(findGlButton().text()).toBe('New issue');
expect(findGlButton().attributes('href')).toBe(defaultProvide.newIssuePath);
});
it('does not render when user does not have permissions', () => {

View File

@ -1,4 +1,4 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import TaskListItemActions from '~/issues/show/components/task_list_item_actions.vue';
import eventHub from '~/issues/show/event_hub';
@ -6,9 +6,9 @@ import eventHub from '~/issues/show/event_hub';
describe('TaskListItemActions component', () => {
let wrapper;
const findGlDropdown = () => wrapper.findComponent(GlDropdown);
const findConvertToTaskItem = () => wrapper.findAllComponents(GlDropdownItem).at(0);
const findDeleteItem = () => wrapper.findAllComponents(GlDropdownItem).at(1);
const findGlDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
const findConvertToTaskItem = () => wrapper.findAllComponents(GlDisclosureDropdownItem).at(0);
const findDeleteItem = () => wrapper.findAllComponents(GlDisclosureDropdownItem).at(1);
const mountComponent = () => {
const li = document.createElement('li');
@ -20,6 +20,7 @@ describe('TaskListItemActions component', () => {
provide: { canUpdate: true },
attachTo: document.querySelector('div'),
});
wrapper.vm.$refs.dropdown.close = jest.fn();
};
beforeEach(() => {
@ -30,8 +31,8 @@ describe('TaskListItemActions component', () => {
expect(findGlDropdown().props()).toMatchObject({
category: 'tertiary',
icon: 'ellipsis_v',
right: true,
text: TaskListItemActions.i18n.taskActions,
placement: 'right',
toggleText: TaskListItemActions.i18n.taskActions,
textSrOnly: true,
});
});
@ -39,7 +40,7 @@ describe('TaskListItemActions component', () => {
it('emits event when `Convert to task` dropdown item is clicked', () => {
jest.spyOn(eventHub, '$emit');
findConvertToTaskItem().vm.$emit('click');
findConvertToTaskItem().vm.$emit('action');
expect(eventHub.$emit).toHaveBeenCalledWith('convert-task-list-item', '3:1-3:10');
});
@ -47,7 +48,7 @@ describe('TaskListItemActions component', () => {
it('emits event when `Delete` dropdown item is clicked', () => {
jest.spyOn(eventHub, '$emit');
findDeleteItem().vm.$emit('click');
findDeleteItem().vm.$emit('action');
expect(eventHub.$emit).toHaveBeenCalledWith('delete-task-list-item', '3:1-3:10');
});

View File

@ -1,8 +1,8 @@
import { GlSkeletonLoader, GlSprintf, GlAlert } from '@gitlab/ui';
import { GlSkeletonLoader, GlSprintf, GlAlert, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import getContainerRepositoriesQuery from 'shared_queries/container_registry/get_container_repositories.query.graphql';
@ -16,6 +16,7 @@ import {
DELETE_IMAGE_SUCCESS_MESSAGE,
DELETE_IMAGE_ERROR_MESSAGE,
SORT_FIELDS,
SETTINGS_TEXT,
} from '~/packages_and_registries/container_registry/explorer/constants';
import deleteContainerRepositoryMutation from '~/packages_and_registries/container_registry/explorer/graphql/mutations/delete_container_repository.mutation.graphql';
import getContainerRepositoriesDetails from '~/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql';
@ -48,6 +49,7 @@ describe('List Page', () => {
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findCliCommands = () => wrapper.findComponent(CliCommands);
const findSettingsLink = () => wrapper.findComponent(GlButton);
const findProjectEmptyState = () => wrapper.findComponent(ProjectEmptyState);
const findGroupEmptyState = () => wrapper.findComponent(GroupEmptyState);
const findRegistryHeader = () => wrapper.findComponent(RegistryHeader);
@ -110,6 +112,9 @@ describe('List Page', () => {
...dockerCommands,
};
},
directives: {
GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
@ -122,6 +127,42 @@ describe('List Page', () => {
expect(findRegistryHeader().props()).toMatchObject({
imagesCount: 2,
metadataLoading: false,
helpPagePath: '',
hideExpirationPolicyData: false,
showCleanupPolicyLink: false,
expirationPolicy: {},
cleanupPoliciesSettingsPath: '',
});
});
describe('link to settings', () => {
beforeEach(() => {
const config = {
showContainerRegistrySettings: true,
cleanupPoliciesSettingsPath: 'bar',
};
mountComponent({ config });
});
it('is rendered', () => {
expect(findSettingsLink().exists()).toBe(true);
});
it('has the right icon', () => {
expect(findSettingsLink().props('icon')).toBe('settings');
});
it('has the right attributes', () => {
expect(findSettingsLink().attributes()).toMatchObject({
'aria-label': SETTINGS_TEXT,
href: 'bar',
});
});
it('sets tooltip with right label', () => {
const tooltip = getBinding(findSettingsLink().element, 'gl-tooltip');
expect(tooltip.value).toBe(SETTINGS_TEXT);
});
});
@ -235,6 +276,14 @@ describe('List Page', () => {
expect(findCliCommands().exists()).toBe(false);
});
it('link to settings is not visible', async () => {
mountComponent({ resolver, config });
await waitForApolloRequestRender();
expect(findSettingsLink().exists()).toBe(false);
});
});
});

View File

@ -105,4 +105,31 @@ describe('MRWidget approvals summary', () => {
expect(wrapper.findComponent(UserAvatarList).exists()).toBe(false);
});
});
describe('user avatars list layout', () => {
beforeEach(() => {
createComponent();
});
it('does not add top padding initially', () => {
const avatarsList = findAvatars();
expect(avatarsList.classes()).not.toContain('gl-pt-1');
});
it('adds some top padding when the list is expanded', async () => {
const avatarsList = findAvatars();
await avatarsList.vm.$emit('expanded');
expect(avatarsList.classes()).toContain('gl-pt-1');
});
it('removes the top padding when the list collapsed', async () => {
const avatarsList = findAvatars();
await avatarsList.vm.$emit('expanded');
await avatarsList.vm.$emit('collapsed');
expect(avatarsList.classes()).not.toContain('gl-pt-1');
});
});
});

View File

@ -148,6 +148,13 @@ describe('UserAvatarList', () => {
expect(links.length).toEqual(TEST_BREAKPOINT);
});
it('does not emit any event on mount', async () => {
factory();
await nextTick();
expect(wrapper.emitted()).toEqual({});
});
describe('with expand clicked', () => {
beforeEach(() => {
factory();
@ -160,13 +167,25 @@ describe('UserAvatarList', () => {
expect(links.length).toEqual(props.items.length);
});
it('with collapse clicked, it renders avatars up to breakpoint', async () => {
clickButton();
it('emits the `expanded` event', () => {
expect(wrapper.emitted('expanded')).toHaveLength(1);
});
await nextTick();
const links = wrapper.findAllComponents(UserAvatarLink);
describe('with collapse clicked', () => {
beforeEach(() => {
clickButton();
});
expect(links.length).toEqual(TEST_BREAKPOINT);
it('renders avatars up to breakpoint', async () => {
await nextTick();
const links = wrapper.findAllComponents(UserAvatarLink);
expect(links.length).toEqual(TEST_BREAKPOINT);
});
it('emits the `collapsed` event', () => {
expect(wrapper.emitted('collapsed')).toHaveLength(1);
});
});
});
});

View File

@ -129,6 +129,62 @@ RSpec.describe PackagesHelper, feature_category: :package_registry do
end
end
describe '#show_container_registry_settings' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:admin) { create(:admin) }
before do
allow(helper).to receive(:current_user) { user }
end
subject { helper.show_container_registry_settings(project) }
context 'with container registry config enabled' do
before do
stub_config(registry: { enabled: true })
end
context 'when user has permission' do
before do
allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(true)
end
it { is_expected.to be(true) }
end
context 'when user does not have permission' do
before do
allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(false)
end
it { is_expected.to be(false) }
end
end
context 'with container registry config disabled' do
before do
stub_config(registry: { enabled: false })
end
context 'when user has permission' do
before do
allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(true)
end
it { is_expected.to be(false) }
end
context 'when user does not have permission' do
before do
allow(Ability).to receive(:allowed?).with(user, :admin_container_image, project).and_return(false)
end
it { is_expected.to be(false) }
end
end
end
describe '#show_group_package_registry_settings' do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }

View File

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe ProtectedTag::CreateAccessLevel, feature_category: :source_code_management do
include_examples 'protected tag access'
describe 'associations' do
it { is_expected.to belong_to(:deploy_key) }
end
@ -10,16 +12,6 @@ RSpec.describe ProtectedTag::CreateAccessLevel, feature_category: :source_code_m
describe 'validations', :aggregate_failures do
let_it_be(:protected_tag) { create(:protected_tag) }
it 'verifies access levels' do
is_expected.to validate_inclusion_of(:access_level).in_array(
[
Gitlab::Access::MAINTAINER,
Gitlab::Access::DEVELOPER,
Gitlab::Access::NO_ACCESS
]
)
end
context 'when deploy key enabled for the project' do
let(:deploy_key) { create(:deploy_key, projects: [protected_tag.project]) }

View File

@ -43,6 +43,7 @@ RSpec.shared_examples "updates atom feed link" do |type|
it "for #{type}" do
sign_in(user)
visit path
click_button 'Actions', match: :first
link = find_link('Subscribe to RSS feed')
params = CGI.parse(URI.parse(link[:href]).query)

View File

@ -0,0 +1,61 @@
# frozen_string_literal: true
RSpec.shared_examples 'protected ref access' do |association|
let_it_be(:project) { create(:project) }
let_it_be(:protected_ref) { create(association, project: project) } # rubocop:disable Rails/SaveBang
it { is_expected.to validate_inclusion_of(:access_level).in_array(described_class.allowed_access_levels) }
it { is_expected.to validate_presence_of(:access_level) }
context 'when not role?' do
before do
allow(subject).to receive(:role?).and_return(false)
end
it { is_expected.not_to validate_presence_of(:access_level) }
end
describe '#check_access' do
let_it_be(:current_user) { create(:user) }
let(:access_level) { ::Gitlab::Access::DEVELOPER }
before_all do
project.add_maintainer(current_user)
end
subject do
described_class.new(
association => protected_ref,
access_level: access_level
)
end
context 'when current_user is nil' do
it { expect(subject.check_access(nil)).to eq(false) }
end
context 'when access_level is NO_ACCESS' do
let(:access_level) { ::Gitlab::Access::NO_ACCESS }
it { expect(subject.check_access(current_user)).to eq(false) }
end
context 'when current_user can push_code to project and access_level is permitted' do
before do
allow(current_user).to receive(:can?).with(:push_code, project).and_return(true)
end
it { expect(subject.check_access(current_user)).to eq(true) }
end
context 'when current_user cannot push_code to project' do
before do
allow(current_user).to receive(:can?).with(:push_code, project).and_return(false)
end
it { expect(subject.check_access(current_user)).to eq(false) }
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
RSpec.shared_examples 'protected tag access' do
include_examples 'protected ref access', :protected_tag
let_it_be(:protected_tag) { create(:protected_tag) }
it { is_expected.to belong_to(:protected_tag) }
describe '#project' do
before do
allow(protected_tag).to receive(:project)
end
it 'delegates project to protected_tag association' do
described_class.new(protected_tag: protected_tag).project
expect(protected_tag).to have_received(:project)
end
end
end