Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
dd240e5cc4
commit
6010cf135a
|
@ -1056,7 +1056,6 @@ Rails/SaveBang:
|
|||
- 'spec/models/note_spec.rb'
|
||||
- 'spec/models/notification_setting_spec.rb'
|
||||
- 'spec/models/operations/feature_flag_scope_spec.rb'
|
||||
- 'spec/models/operations/feature_flag_spec.rb'
|
||||
- 'spec/models/operations/feature_flags/strategy_spec.rb'
|
||||
- 'spec/models/operations/feature_flags/user_list_spec.rb'
|
||||
- 'spec/models/pages_domain_spec.rb'
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
<script>
|
||||
/* eslint-disable @gitlab/vue-require-i18n-strings */
|
||||
import { GlTabs, GlTab, GlBadge } from '@gitlab/ui';
|
||||
import ModalStore from '../../stores/modal_store';
|
||||
import modalMixin from '../../mixins/modal_mixins';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlTabs,
|
||||
GlTab,
|
||||
GlBadge,
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
|
@ -19,18 +25,18 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="top-area gl-mt-3 gl-mb-3">
|
||||
<ul class="nav-links issues-state-filters">
|
||||
<li :class="{ active: activeTab == 'all' }">
|
||||
<a href="#" role="button" @click.prevent="changeTab('all')">
|
||||
Open issues <span class="badge badge-pill"> {{ issuesCount }} </span>
|
||||
</a>
|
||||
</li>
|
||||
<li :class="{ active: activeTab == 'selected' }">
|
||||
<a href="#" role="button" @click.prevent="changeTab('selected')">
|
||||
Selected issues <span class="badge badge-pill"> {{ selectedCount }} </span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<gl-tabs class="gl-mt-3">
|
||||
<gl-tab @click.prevent="changeTab('all')">
|
||||
<template slot="title">
|
||||
<span>Open issues</span>
|
||||
<gl-badge size="sm" class="gl-tab-counter-badge">{{ issuesCount }}</gl-badge>
|
||||
</template>
|
||||
</gl-tab>
|
||||
<gl-tab @click.prevent="changeTab('selected')">
|
||||
<template slot="title">
|
||||
<span>Selected issues</span>
|
||||
<gl-badge size="sm" class="gl-tab-counter-badge">{{ selectedCount }}</gl-badge>
|
||||
</template>
|
||||
</gl-tab>
|
||||
</gl-tabs>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
|
||||
import { getLocationHash, doesHashExistInUrl } from '../../lib/utils/url_utility';
|
||||
import {
|
||||
DISCUSSION_FILTERS_DEFAULT_VALUE,
|
||||
|
@ -14,7 +13,9 @@ import notesEventHub from '../event_hub';
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownDivider,
|
||||
},
|
||||
props: {
|
||||
filters: {
|
||||
|
@ -66,9 +67,6 @@ export default {
|
|||
selectFilter(value, persistFilter = true) {
|
||||
const filter = parseInt(value, 10);
|
||||
|
||||
// close dropdown
|
||||
this.toggleDropdown();
|
||||
|
||||
if (filter === this.currentValue) return;
|
||||
this.currentValue = filter;
|
||||
this.filterDiscussion({
|
||||
|
@ -78,9 +76,6 @@ export default {
|
|||
});
|
||||
this.toggleCommentsForm();
|
||||
},
|
||||
toggleDropdown() {
|
||||
$(this.$refs.dropdownToggle).dropdown('toggle');
|
||||
},
|
||||
toggleCommentsForm() {
|
||||
this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE);
|
||||
},
|
||||
|
@ -92,7 +87,6 @@ export default {
|
|||
|
||||
if (/^note_/.test(hash) && this.currentValue !== DISCUSSION_FILTERS_DEFAULT_VALUE) {
|
||||
this.selectFilter(this.defaultValue, false);
|
||||
this.toggleDropdown(); // close dropdown
|
||||
this.setTargetNoteHash(hash);
|
||||
}
|
||||
},
|
||||
|
@ -109,43 +103,24 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
<gl-dropdown
|
||||
v-if="displayFilters"
|
||||
class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom full-width-mobile"
|
||||
id="discussion-filter-dropdown"
|
||||
class="gl-mr-3 full-width-mobile discussion-filter-container js-discussion-filter-container qa-discussion-filter"
|
||||
:text="currentFilter.title"
|
||||
>
|
||||
<button
|
||||
id="discussion-filter-dropdown"
|
||||
ref="dropdownToggle"
|
||||
class="btn btn-sm qa-discussion-filter"
|
||||
data-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{{ currentFilter.title }} <gl-icon name="chevron-down" />
|
||||
</button>
|
||||
<div
|
||||
ref="dropdownMenu"
|
||||
class="dropdown-menu dropdown-menu-selectable dropdown-menu-right"
|
||||
aria-labelledby="discussion-filter-dropdown"
|
||||
>
|
||||
<div class="dropdown-content">
|
||||
<ul>
|
||||
<li
|
||||
v-for="filter in filters"
|
||||
:key="filter.value"
|
||||
:data-filter-type="filterType(filter.value)"
|
||||
>
|
||||
<button
|
||||
:class="{ 'is-active': filter.value === currentValue }"
|
||||
class="qa-filter-options"
|
||||
type="button"
|
||||
@click="selectFilter(filter.value)"
|
||||
>
|
||||
{{ filter.title }}
|
||||
</button>
|
||||
<div v-if="filter.value === defaultValue" class="dropdown-divider"></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-for="filter in filters" :key="filter.value" class="dropdown-item-wrapper">
|
||||
<gl-dropdown-item
|
||||
:is-check-item="true"
|
||||
:is-checked="filter.value === currentValue"
|
||||
:class="{ 'is-active': filter.value === currentValue }"
|
||||
:data-filter-type="filterType(filter.value)"
|
||||
class="qa-filter-options"
|
||||
@click.prevent="selectFilter(filter.value)"
|
||||
>
|
||||
{{ filter.title }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider v-if="filter.value === defaultValue" />
|
||||
</div>
|
||||
</div>
|
||||
</gl-dropdown>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
gs
|
||||
<script>
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { __ } from '~/locale';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
|
@ -15,7 +14,8 @@ const SORT_OPTIONS = [
|
|||
export default {
|
||||
SORT_OPTIONS,
|
||||
components: {
|
||||
GlIcon,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
LocalStorageSync,
|
||||
},
|
||||
mixins: [Tracking.mixin()],
|
||||
|
@ -49,33 +49,27 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-testid="sort-discussion-filter"
|
||||
class="gl-mr-2 gl-display-inline-block gl-vertical-align-bottom full-width-mobile"
|
||||
>
|
||||
<div class="gl-mr-3 gl-display-inline-block gl-vertical-align-bottom full-width-mobile">
|
||||
<local-storage-sync
|
||||
:value="sortDirection"
|
||||
:storage-key="storageKey"
|
||||
@input="setDiscussionSortDirection"
|
||||
/>
|
||||
<button class="btn btn-sm js-dropdown-text" data-toggle="dropdown" aria-expanded="false">
|
||||
{{ dropdownText }}
|
||||
<gl-icon name="chevron-down" />
|
||||
</button>
|
||||
<div ref="dropdownMenu" class="dropdown-menu dropdown-menu-selectable dropdown-menu-right">
|
||||
<div class="dropdown-content">
|
||||
<ul>
|
||||
<li v-for="{ text, key, cls } in $options.SORT_OPTIONS" :key="key">
|
||||
<button
|
||||
:class="[cls, { 'is-active': isDropdownItemActive(key) }]"
|
||||
type="button"
|
||||
@click="fetchSortedDiscussions(key)"
|
||||
>
|
||||
{{ text }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<gl-dropdown
|
||||
:text="dropdownText"
|
||||
data-testid="sort-discussion-filter"
|
||||
class="js-dropdown-text full-width-mobile"
|
||||
>
|
||||
<gl-dropdown-item
|
||||
v-for="{ text, key, cls } in $options.SORT_OPTIONS"
|
||||
:key="key"
|
||||
:class="cls"
|
||||
:is-check-item="true"
|
||||
:is-checked="isDropdownItemActive(key)"
|
||||
@click="fetchSortedDiscussions(key)"
|
||||
>
|
||||
{{ text }}
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
<script>
|
||||
import { GlDeprecatedButton, GlIcon } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlIcon,
|
||||
GlDeprecatedButton,
|
||||
},
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
primaryButtonClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
dropdownClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
actions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
defaultAction: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedAction: this.defaultAction,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selectedActionTitle() {
|
||||
return this.actions[this.selectedAction].title;
|
||||
},
|
||||
buttonSizeClass() {
|
||||
return `btn-${this.size}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handlePrimaryActionClick() {
|
||||
this.$emit('onActionClick', this.actions[this.selectedAction]);
|
||||
},
|
||||
handleActionClick(selectedAction) {
|
||||
this.selectedAction = selectedAction;
|
||||
this.$emit('onActionSelect', selectedAction);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="btn-group droplab-dropdown comment-type-dropdown">
|
||||
<gl-deprecated-button
|
||||
:class="primaryButtonClass"
|
||||
:size="size"
|
||||
@click.prevent="handlePrimaryActionClick"
|
||||
>
|
||||
{{ selectedActionTitle }}
|
||||
</gl-deprecated-button>
|
||||
<button
|
||||
:class="buttonSizeClass"
|
||||
type="button"
|
||||
class="btn dropdown-toggle pl-2 pr-2"
|
||||
data-display="static"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<gl-icon name="chevron-down" :aria-label="__('toggle dropdown')" />
|
||||
</button>
|
||||
<ul :class="dropdownClass" class="dropdown-menu dropdown-open-top">
|
||||
<template v-for="(action, index) in actions">
|
||||
<li :key="index" :class="{ 'droplab-item-selected': selectedAction === index }">
|
||||
<gl-deprecated-button class="btn-transparent" @click.prevent="handleActionClick(index)">
|
||||
<i aria-hidden="true" class="fa fa-check icon"> </i>
|
||||
<div class="description">
|
||||
<strong>{{ action.title }}</strong>
|
||||
<p>{{ action.description }}</p>
|
||||
</div>
|
||||
</gl-deprecated-button>
|
||||
</li>
|
||||
<li v-if="index === 0" :key="`${index}-separator`" class="divider droplab-item-ignore"></li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
|
@ -1,19 +1,16 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import { GlDeprecatedButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import Clipboard from 'clipboard';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDeprecatedButton,
|
||||
GlIcon,
|
||||
GlButton,
|
||||
},
|
||||
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
|
@ -55,15 +52,12 @@ export default {
|
|||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
copySuccessText: __('Copied'),
|
||||
|
||||
computed: {
|
||||
modalDomId() {
|
||||
return this.modalId ? `#${this.modalId}` : '';
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.clipboard = new Clipboard(this.$el, {
|
||||
|
@ -83,13 +77,11 @@ export default {
|
|||
.on('error', e => this.$emit('error', e));
|
||||
});
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
if (this.clipboard) {
|
||||
this.clipboard.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateTooltip(target) {
|
||||
const $target = $(target);
|
||||
|
@ -112,15 +104,12 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-deprecated-button
|
||||
<gl-button
|
||||
v-gl-tooltip="{ placement: tooltipPlacement, container: tooltipContainer }"
|
||||
:class="cssClasses"
|
||||
:data-clipboard-target="target"
|
||||
:data-clipboard-text="text"
|
||||
:title="title"
|
||||
>
|
||||
<slot>
|
||||
<gl-icon name="copy-to-clipboard" />
|
||||
</slot>
|
||||
</gl-deprecated-button>
|
||||
icon="copy-to-clipboard"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class BoardResolver < BaseResolver.single
|
||||
alias_method :parent, :synchronized_object
|
||||
|
||||
type Types::BoardType, null: true
|
||||
|
||||
argument :id, GraphQL::ID_TYPE,
|
||||
required: true,
|
||||
description: 'The board\'s ID'
|
||||
|
||||
def resolve(id: nil)
|
||||
return unless parent
|
||||
|
||||
::Boards::ListService.new(parent, context[:current_user], board_id: extract_board_id(id)).execute(create_default_board: false).first
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extract_board_id(gid)
|
||||
GitlabSchema.parse_gid(gid, expected_type: ::Board).model_id
|
||||
end
|
||||
end
|
||||
end
|
|
@ -64,7 +64,7 @@ module Types
|
|||
Types::BoardType,
|
||||
null: true,
|
||||
description: 'A single board of the group',
|
||||
resolver: Resolvers::BoardsResolver.single
|
||||
resolver: Resolvers::BoardResolver
|
||||
|
||||
field :label,
|
||||
Types::LabelType,
|
||||
|
|
|
@ -234,7 +234,7 @@ module Types
|
|||
Types::BoardType,
|
||||
null: true,
|
||||
description: 'A single board of the project',
|
||||
resolver: Resolvers::BoardsResolver.single
|
||||
resolver: Resolvers::BoardResolver
|
||||
|
||||
field :jira_imports,
|
||||
Types::JiraImportType.connection_type,
|
||||
|
|
|
@ -770,7 +770,7 @@ module ProjectsHelper
|
|||
def project_access_token_available?(project)
|
||||
return false if ::Gitlab.com?
|
||||
|
||||
::Feature.enabled?(:resource_access_token, project, default_enabled: true)
|
||||
can?(current_user, :admin_resource_access_tokens, project)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Verifies features availability based on issue type.
|
||||
# This can be used, for example, for hiding UI elements or blocking specific
|
||||
# quick actions for particular issue types;
|
||||
module IssueAvailableFeatures
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# EE only features are listed on EE::IssueAvailableFeatures
|
||||
def available_features_for_issue_types
|
||||
{}.with_indifferent_access
|
||||
end
|
||||
|
||||
def issue_type_supports?(feature)
|
||||
unless available_features_for_issue_types.has_key?(feature)
|
||||
raise ArgumentError, 'invalid feature'
|
||||
end
|
||||
|
||||
available_features_for_issue_types[feature].include?(issue_type)
|
||||
end
|
||||
end
|
||||
|
||||
IssueAvailableFeatures.prepend_if_ee('EE::IssueAvailableFeatures')
|
|
@ -20,6 +20,7 @@ class Issue < ApplicationRecord
|
|||
include StateEventable
|
||||
include IdInOrdered
|
||||
include Presentable
|
||||
include IssueAvailableFeatures
|
||||
|
||||
DueDateStruct = Struct.new(:title, :name).freeze
|
||||
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
|
||||
|
|
|
@ -56,6 +56,9 @@ class GroupPolicy < BasePolicy
|
|||
@user.is_a?(DeployToken) && @user.groups.include?(@subject) && @user.write_package_registry
|
||||
end
|
||||
|
||||
with_scope :subject
|
||||
condition(:resource_access_token_available) { resource_access_token_available? }
|
||||
|
||||
rule { design_management_enabled }.policy do
|
||||
enable :read_design_activity
|
||||
end
|
||||
|
@ -187,6 +190,10 @@ class GroupPolicy < BasePolicy
|
|||
enable :read_group
|
||||
end
|
||||
|
||||
rule { resource_access_token_available & can?(:admin_group) }.policy do
|
||||
enable :admin_resource_access_tokens
|
||||
end
|
||||
|
||||
def access_level
|
||||
return GroupMember::NO_ACCESS if @user.nil?
|
||||
return GroupMember::NO_ACCESS unless user_is_user?
|
||||
|
@ -203,6 +210,14 @@ class GroupPolicy < BasePolicy
|
|||
def user_is_user?
|
||||
user.is_a?(User)
|
||||
end
|
||||
|
||||
def group
|
||||
@subject
|
||||
end
|
||||
|
||||
def resource_access_token_available?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
GroupPolicy.prepend_if_ee('EE::GroupPolicy')
|
||||
|
|
|
@ -104,6 +104,9 @@ class ProjectPolicy < BasePolicy
|
|||
with_scope :subject
|
||||
condition(:service_desk_enabled) { @subject.service_desk_enabled? }
|
||||
|
||||
with_scope :subject
|
||||
condition(:resource_access_token_available) { resource_access_token_available? }
|
||||
|
||||
# We aren't checking `:read_issue` or `:read_merge_request` in this case
|
||||
# because it could be possible for a user to see an issuable-iid
|
||||
# (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
|
||||
|
@ -589,6 +592,10 @@ class ProjectPolicy < BasePolicy
|
|||
prevent :read_project
|
||||
end
|
||||
|
||||
rule { resource_access_token_available & can?(:admin_project) }.policy do
|
||||
enable :admin_resource_access_tokens
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_is_user?
|
||||
|
@ -663,6 +670,10 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
end
|
||||
|
||||
def resource_access_token_available?
|
||||
true
|
||||
end
|
||||
|
||||
def project
|
||||
@subject
|
||||
end
|
||||
|
|
|
@ -34,9 +34,7 @@ module Git
|
|||
def can_process_wiki_events?
|
||||
# TODO: Support activity events for group wikis
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/209306
|
||||
return false unless wiki.is_a?(ProjectWiki)
|
||||
|
||||
Feature.enabled?(:wiki_events_on_git_push, wiki.container)
|
||||
wiki.is_a?(ProjectWiki)
|
||||
end
|
||||
|
||||
def push_changes
|
||||
|
|
|
@ -32,20 +32,11 @@ module ResourceAccessTokens
|
|||
attr_reader :resource_type, :resource
|
||||
|
||||
def feature_enabled?
|
||||
return false if ::Gitlab.com?
|
||||
|
||||
::Feature.enabled?(:resource_access_token, resource, default_enabled: true)
|
||||
return true unless ::Gitlab.com?
|
||||
end
|
||||
|
||||
def has_permission_to_create?
|
||||
case resource_type
|
||||
when 'project'
|
||||
can?(current_user, :admin_project, resource)
|
||||
when 'group'
|
||||
can?(current_user, :admin_group, resource)
|
||||
else
|
||||
false
|
||||
end
|
||||
%w(project group).include?(resource_type) && can?(current_user, :admin_resource_access_tokens, resource)
|
||||
end
|
||||
|
||||
def create_user
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'GraphQL: No longer allows to omit ID when querying for a single board.'
|
||||
merge_request: 43627
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update issue boards modal to gl-tabs
|
||||
merge_request: 43740
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Enable wiki events on git push
|
||||
merge_request: 43738
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update button in modal_copy_button.vue to use GlButton from GitLab UI
|
||||
merge_request: 43714
|
||||
author:
|
||||
type: other
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: coverage_report_view
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/21791
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/211410
|
||||
group: 'group::verify testing'
|
||||
type: development
|
||||
default_enabled: true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: junit_pipeline_screenshots_view
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/202114
|
||||
rollout_issue_url:
|
||||
group: 'group::verify testing'
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
name: wiki_events_on_git_push
|
||||
introduced_by_url:
|
||||
rollout_issue_url:
|
||||
group:
|
||||
type: development
|
||||
default_enabled: false
|
|
@ -3,5 +3,5 @@ name: resource_access_token
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/29622
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/235765
|
||||
group: group::access
|
||||
type: development
|
||||
type: licensed
|
||||
default_enabled: true
|
|
@ -731,6 +731,7 @@ Gitlab.ee do
|
|||
Settings['kerberos'] ||= Settingslogic.new({})
|
||||
Settings.kerberos['enabled'] = false if Settings.kerberos['enabled'].nil?
|
||||
Settings.kerberos['keytab'] = nil if Settings.kerberos['keytab'].blank? # nil means use default keytab
|
||||
Settings.kerberos['simple_ldap_linking_allowed_realms'] = [] if Settings.kerberos['simple_ldap_linking_allowed_realms'].blank?
|
||||
Settings.kerberos['service_principal_name'] = nil if Settings.kerberos['service_principal_name'].blank? # nil means any SPN in keytab
|
||||
Settings.kerberos['use_dedicated_port'] = false if Settings.kerberos['use_dedicated_port'].nil?
|
||||
Settings.kerberos['https'] = Settings.gitlab.https if Settings.kerberos['https'].nil?
|
||||
|
|
|
@ -278,6 +278,14 @@ module.exports = {
|
|||
chunks: 'initial',
|
||||
minChunks: autoEntriesCount * 0.9,
|
||||
}),
|
||||
graphql: {
|
||||
priority: 16,
|
||||
name: 'graphql',
|
||||
chunks: 'all',
|
||||
test: /[\\/]node_modules[\\/][^\\/]*(immer|apollo|graphql|zen-observable)[^\\/]*[\\/]/,
|
||||
minChunks: 2,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
monaco: {
|
||||
priority: 15,
|
||||
name: 'monaco',
|
||||
|
|
|
@ -309,21 +309,22 @@ The following table gives an overview of how the API functions generally behave.
|
|||
|
||||
The following table shows the possible return codes for API requests.
|
||||
|
||||
| Return values | Description |
|
||||
| ------------- | ----------- |
|
||||
| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. |
|
||||
| `204 No Content` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. |
|
||||
| `201 Created` | The `POST` request was successful and the resource is returned as JSON. |
|
||||
| `304 Not Modified` | Indicates that the resource has not been modified since the last request. |
|
||||
| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. |
|
||||
| `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. |
|
||||
| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. |
|
||||
| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. |
|
||||
| Return values | Description |
|
||||
| ------------------------ | ----------- |
|
||||
| `200 OK` | The `GET`, `PUT` or `DELETE` request was successful, the resource(s) itself is returned as JSON. |
|
||||
| `204 No Content` | The server has successfully fulfilled the request and that there is no additional content to send in the response payload body. |
|
||||
| `201 Created` | The `POST` request was successful and the resource is returned as JSON. |
|
||||
| `304 Not Modified` | Indicates that the resource has not been modified since the last request. |
|
||||
| `400 Bad Request` | A required attribute of the API request is missing, e.g., the title of an issue is not given. |
|
||||
| `401 Unauthorized` | The user is not authenticated, a valid [user token](#authentication) is necessary. |
|
||||
| `403 Forbidden` | The request is not allowed, e.g., the user is not allowed to delete a project. |
|
||||
| `404 Not Found` | A resource could not be accessed, e.g., an ID for a resource could not be found. |
|
||||
| `405 Method Not Allowed` | The request is not supported. |
|
||||
| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. |
|
||||
| `412` | Indicates the request was denied. May happen if the `If-Unmodified-Since` header is provided when trying to delete a resource, which was modified in between. |
|
||||
| `422 Unprocessable` | The entity could not be processed. |
|
||||
| `500 Server Error` | While handling the request something went wrong server-side. |
|
||||
| `409 Conflict` | A conflicting resource already exists, e.g., creating a project with a name that already exists. |
|
||||
| `412` | Indicates the request was denied. May happen if the `If-Unmodified-Since` header is provided when trying to delete a resource, which was modified in between. |
|
||||
| `422 Unprocessable` | The entity could not be processed. |
|
||||
| `429 Too Many Requests` | The user exceeded the [application rate limits](../administration/instance_limits.md#rate-limits). |
|
||||
| `500 Server Error` | While handling the request, something went wrong server-side. |
|
||||
|
||||
## Pagination
|
||||
|
||||
|
|
|
@ -7125,9 +7125,9 @@ type Group {
|
|||
"""
|
||||
board(
|
||||
"""
|
||||
Find a board by its ID
|
||||
The board's ID
|
||||
"""
|
||||
id: ID
|
||||
id: ID!
|
||||
): Board
|
||||
|
||||
"""
|
||||
|
@ -12761,9 +12761,9 @@ type Project {
|
|||
"""
|
||||
board(
|
||||
"""
|
||||
Find a board by its ID
|
||||
The board's ID
|
||||
"""
|
||||
id: ID
|
||||
id: ID!
|
||||
): Board
|
||||
|
||||
"""
|
||||
|
|
|
@ -19764,11 +19764,15 @@
|
|||
"args": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "Find a board by its ID",
|
||||
"description": "The board's ID",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
|
@ -37719,11 +37723,15 @@
|
|||
"args": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "Find a board by its ID",
|
||||
"description": "The board's ID",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ that were able to quickly complete this migration:
|
|||
1. Use the [Jenkins Wrapper](#jenkinsfile-wrapper) to temporarily maintain fragile Jenkins jobs.
|
||||
1. Migrate the build and CI jobs and configure them to show results directly in your merge requests. They can use [Auto DevOps](../../topics/autodevops/index.md) as a starting point, and [customize](../../topics/autodevops/customize.md) or [decompose](../../topics/autodevops/customize.md#using-components-of-auto-devops) the configuration as needed.
|
||||
1. Add [Review Apps](../review_apps/index.md).
|
||||
1. Migrate the deployment jobs using [cloud deployment templates](../cloud_deployment/index.md), adding [environments](../environments/index.md), and [deploy boards](../..//user/project/deploy_boards.md).
|
||||
1. Migrate the deployment jobs using [cloud deployment templates](../cloud_deployment/index.md), adding [environments](../environments/index.md), and [deploy boards](../../user/project/deploy_boards.md).
|
||||
1. Work to unwrap any jobs still running with the use of the Jenkins wrapper.
|
||||
1. Take stock of any common CI/CD job definitions then create and share [templates](#templates) for them.
|
||||
1. Check the [pipeline efficiency documentation](../pipelines/pipeline_efficiency.md)
|
||||
|
|
|
@ -625,6 +625,13 @@ To get around this, you can [change the group path](../../group/index.md#changin
|
|||
[change the project path](../../project/settings/index.md#renaming-a-repository) or change the branch
|
||||
name.
|
||||
|
||||
You may also get a `404 Not Found` or `Unknown Manifest` message if you are using
|
||||
a Docker Engine version earlier than 17.12. Later versions of Docker Engine use
|
||||
[the v2 API](https://docs.docker.com/registry/spec/manifest-v2-2/).
|
||||
|
||||
The images in your GitLab Container Registry must also use the Docker v2 API.
|
||||
For information on how to update your images, see the [Docker help](https://docs.docker.com/registry/spec/deprecated-schema-v1).
|
||||
|
||||
### Troubleshoot as a GitLab server admin
|
||||
|
||||
Troubleshooting the GitLab Container Registry, most of the times, requires
|
||||
|
|
|
@ -434,5 +434,8 @@ npm dist-tag rm @scope/package@version my-tag # Delete a tag from the package
|
|||
npm install @scope/package@my-tag # Install a specific tag
|
||||
```
|
||||
|
||||
NOTE: **Note:**
|
||||
You cannot use your `CI_JOB_TOKEN` or deploy token with the `npm dist-tag` commands. View [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/258835) for details.
|
||||
|
||||
CAUTION: **Warning:**
|
||||
Due to a bug in NPM 6.9.0, deleting dist tags fails. Make sure your NPM version is greater than 6.9.1.
|
||||
|
|
|
@ -79,6 +79,9 @@ This will enable the `Bug` dropdown option when creating or editing issues. When
|
|||
to the issue description field. The 'Reset template' button will discard any
|
||||
changes you made after picking the template and return it to its initial status.
|
||||
|
||||
TIP: **Tip:**
|
||||
You can create short-cut links to create an issue using a designated template. For example: `https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Feature%20proposal`.
|
||||
|
||||
![Description templates](img/description_templates.png)
|
||||
|
||||
## Setting a default template for merge requests and issues **(STARTER)**
|
||||
|
|
|
@ -8,8 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/5554) in [GitLab 8.11](https://about.gitlab.com/releases/2016/08/22/gitlab-8-11-released/#issue-board).
|
||||
|
||||
## Overview
|
||||
|
||||
The GitLab Issue Board is a software project management tool used to plan,
|
||||
organize, and visualize a workflow for a feature or product release.
|
||||
It can be used as a [Kanban](https://en.wikipedia.org/wiki/Kanban_(development)) or a
|
||||
|
|
|
@ -8,8 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/1904) in [GitLab Starter 9.2](https://about.gitlab.com/releases/2017/05/22/gitlab-9-2-released/#multiple-assignees-for-issues).
|
||||
|
||||
## Overview
|
||||
|
||||
In large teams, where there is shared ownership of an issue, it can be difficult
|
||||
to track who is working on it, who already completed their contributions, who
|
||||
didn't even start yet.
|
||||
|
|
|
@ -6,8 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Labels
|
||||
|
||||
## Overview
|
||||
|
||||
As your count of issues, merge requests, and epics grows in GitLab, it's more and more challenging
|
||||
to keep track of those items. Especially as your organization grows from just a few people to
|
||||
hundreds or thousands. This is where labels come in. They help you organize and tag your work
|
||||
|
|
|
@ -14,8 +14,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> value, so the burndown chart considers them as closed on the milestone
|
||||
> `start_date`. In that case, a warning will be displayed.
|
||||
|
||||
## Overview
|
||||
|
||||
Burndown Charts are visual representations of the progress of completing a milestone.
|
||||
|
||||
![burndown chart](img/burndown_chart.png)
|
||||
|
|
|
@ -7,8 +7,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Milestones
|
||||
|
||||
## Overview
|
||||
|
||||
Milestones in GitLab are a way to track issues and merge requests created to achieve a broader goal in a certain period of time.
|
||||
|
||||
Milestones allow you to organize issues and merge requests into a cohesive group, with an optional start date and an optional due date.
|
||||
|
|
|
@ -10,8 +10,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/214839) to [GitLab Starter](https://about.gitlab.com/pricing/) in 13.0.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/215364) to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2.
|
||||
|
||||
## Overview
|
||||
|
||||
Service Desk is a module that allows your team to connect directly
|
||||
with any external party through email right inside of GitLab; no external tools required.
|
||||
An ongoing conversation right where your software is built ensures that user feedback ends
|
||||
|
|
|
@ -163,48 +163,13 @@ Similar to versioned diff file views, you can see the changes made in a given Wi
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/14902) in **GitLab 12.10.**
|
||||
> - Git events were [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216014) in **GitLab 13.0.**
|
||||
> - It's enabled on GitLab.com.
|
||||
> - Git access activity creation is managed by a feature flag.
|
||||
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-wiki-events-in-git). **(CORE ONLY)**
|
||||
> - [Feature flag for Git events was removed](https://gitlab.com/gitlab-org/gitlab/-/issues/258665) in **GitLab 13.5**
|
||||
|
||||
Wiki events (creation, deletion, and updates) are tracked by GitLab and
|
||||
displayed on the [user profile](../../profile/index.md#user-profile),
|
||||
[group](../../group/index.md#view-group-activity),
|
||||
and [project](../index.md#project-activity) activity pages.
|
||||
|
||||
### Enable or disable Wiki events in Git **(CORE ONLY)**
|
||||
|
||||
Tracking wiki events through Git is under development and not ready for production use. It is
|
||||
deployed behind a feature flag that is **disabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
|
||||
can enable it for your instance.
|
||||
|
||||
To enable it:
|
||||
|
||||
```ruby
|
||||
Feature.enable(:wiki_events_on_git_push)
|
||||
```
|
||||
|
||||
To enable for just a particular project:
|
||||
|
||||
```ruby
|
||||
project = Project.find_by_full_path('your-group/your-project')
|
||||
Feature.enable(:wiki_events_on_git_push, project)
|
||||
```
|
||||
|
||||
To disable it:
|
||||
|
||||
```ruby
|
||||
Feature.disable(:wiki_events_on_git_push)
|
||||
```
|
||||
|
||||
To disable for just a particular project:
|
||||
|
||||
```ruby
|
||||
project = Project.find_by_full_path('your-group/your-project')
|
||||
Feature.disable(:wiki_events_on_git_push, project)
|
||||
```
|
||||
|
||||
## Adding and editing wiki pages locally
|
||||
|
||||
Since wikis are based on Git repositories, you can clone them locally and edit
|
||||
|
|
|
@ -31241,9 +31241,6 @@ msgstr ""
|
|||
msgid "toggle collapse"
|
||||
msgstr ""
|
||||
|
||||
msgid "toggle dropdown"
|
||||
msgstr ""
|
||||
|
||||
msgid "triggered"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -76,6 +76,15 @@ module QA
|
|||
parse_body(response)[:title].include?(title)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def api_get
|
||||
with_paginated_response_body(Runtime::API::Request.new(api_client, '/user/keys', per_page: '100').url) do |page|
|
||||
key = page.find { |key| key[:title] == title }
|
||||
break process_api_response(key) if key
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,6 +36,10 @@ module QA
|
|||
Flow::Login.sign_in
|
||||
end
|
||||
|
||||
after do
|
||||
ssh_key.remove_via_api!
|
||||
end
|
||||
|
||||
it 'clones, pushes, and pulls a snippet over HTTP, edits via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/826' do
|
||||
Resource::Repository::Push.fabricate! do |push|
|
||||
push.repository_http_uri = repository_uri_http
|
||||
|
|
|
@ -36,6 +36,10 @@ module QA
|
|||
Flow::Login.sign_in
|
||||
end
|
||||
|
||||
after do
|
||||
ssh_key.remove_via_api!
|
||||
end
|
||||
|
||||
it 'clones, pushes, and pulls a project snippet over HTTP, edits via UI', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/833' do
|
||||
Resource::Repository::Push.fabricate! do |push|
|
||||
push.repository_http_uri = repository_uri_http
|
||||
|
|
|
@ -14,28 +14,21 @@ RSpec.describe Projects::Settings::AccessTokensController do
|
|||
sign_in(user)
|
||||
end
|
||||
|
||||
shared_examples 'feature unavailability' do
|
||||
context 'when flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(resource_access_token: false)
|
||||
end
|
||||
shared_examples 'feature unavailable' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(false)
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'when environment is Gitlab.com' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
describe '#index' do
|
||||
subject { get :index, params: { namespace_id: project.namespace, project_id: project } }
|
||||
|
||||
it_behaves_like 'feature unavailability'
|
||||
it_behaves_like 'feature unavailable'
|
||||
|
||||
context 'when feature is available' do
|
||||
let_it_be(:bot_user) { create(:user, :project_bot) }
|
||||
|
@ -84,7 +77,7 @@ RSpec.describe Projects::Settings::AccessTokensController do
|
|||
|
||||
let_it_be(:access_token_params) { {} }
|
||||
|
||||
it_behaves_like 'feature unavailability'
|
||||
it_behaves_like 'feature unavailable'
|
||||
|
||||
context 'when feature is available' do
|
||||
let_it_be(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: 1.month.since.to_date } }
|
||||
|
@ -148,7 +141,7 @@ RSpec.describe Projects::Settings::AccessTokensController do
|
|||
project.add_maintainer(bot_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'feature unavailability'
|
||||
it_behaves_like 'feature unavailable'
|
||||
|
||||
context 'when feature is available' do
|
||||
before do
|
||||
|
@ -185,6 +178,5 @@ RSpec.describe Projects::Settings::AccessTokensController do
|
|||
|
||||
def enable_feature
|
||||
allow(Gitlab).to receive(:com?).and_return(false)
|
||||
stub_feature_flags(resource_access_token: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -79,7 +79,7 @@ RSpec.describe 'Issue Boards add issue modal', :js do
|
|||
|
||||
it 'loads issues' do
|
||||
page.within('.add-issues-modal') do
|
||||
page.within('.nav-links') do
|
||||
page.within('.gl-tabs') do
|
||||
expect(page).to have_content('2')
|
||||
end
|
||||
|
||||
|
@ -146,7 +146,7 @@ RSpec.describe 'Issue Boards add issue modal', :js do
|
|||
page.within('.add-issues-modal') do
|
||||
first('.board-card .board-card-number').click
|
||||
|
||||
page.within('.nav-links') do
|
||||
page.within('.gl-tabs') do
|
||||
expect(page).to have_content('Selected issues 1')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,6 +13,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
|
|||
create(:milestone,
|
||||
project: project,
|
||||
title: '12.3',
|
||||
description: 'The 12.3 milestone',
|
||||
start_date: Time.zone.parse('2018-12-10'),
|
||||
due_date: Time.zone.parse('2019-01-10'))
|
||||
end
|
||||
|
@ -21,6 +22,7 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
|
|||
create(:milestone,
|
||||
project: project,
|
||||
title: '12.4',
|
||||
description: 'The 12.4 milestone',
|
||||
start_date: Time.zone.parse('2019-01-10'),
|
||||
due_date: Time.zone.parse('2019-02-10'))
|
||||
end
|
||||
|
@ -65,10 +67,26 @@ RSpec.describe 'Releases (JavaScript fixtures)' do
|
|||
create(:release_link,
|
||||
release: release,
|
||||
name: 'Runbook',
|
||||
url: 'https://example.com/runbook',
|
||||
url: "#{release.project.web_url}/runbook",
|
||||
link_type: :runbook)
|
||||
end
|
||||
|
||||
let_it_be(:package_link) do
|
||||
create(:release_link,
|
||||
release: release,
|
||||
name: 'Package',
|
||||
url: 'https://example.com/package',
|
||||
link_type: :package)
|
||||
end
|
||||
|
||||
let_it_be(:image_link) do
|
||||
create(:release_link,
|
||||
release: release,
|
||||
name: 'Image',
|
||||
url: 'https://example.com/image',
|
||||
link_type: :image)
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
remove_repository(project)
|
||||
end
|
||||
|
|
|
@ -74,13 +74,15 @@ describe('DiscussionFilter component', () => {
|
|||
});
|
||||
|
||||
it('renders the all filters', () => {
|
||||
expect(wrapper.findAll('.dropdown-menu li').length).toBe(discussionFiltersMock.length);
|
||||
expect(wrapper.findAll('.discussion-filter-container .dropdown-item').length).toBe(
|
||||
discussionFiltersMock.length,
|
||||
);
|
||||
});
|
||||
|
||||
it('renders the default selected item', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('#discussion-filter-dropdown')
|
||||
.find('#discussion-filter-dropdown .dropdown-item')
|
||||
.text()
|
||||
.trim(),
|
||||
).toBe(discussionFiltersMock[0].title);
|
||||
|
@ -88,7 +90,7 @@ describe('DiscussionFilter component', () => {
|
|||
|
||||
it('updates to the selected item', () => {
|
||||
const filterItem = wrapper.find(
|
||||
`.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`,
|
||||
`.discussion-filter-container .dropdown-item[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"]`,
|
||||
);
|
||||
|
||||
filterItem.trigger('click');
|
||||
|
@ -98,7 +100,9 @@ describe('DiscussionFilter component', () => {
|
|||
|
||||
it('only updates when selected filter changes', () => {
|
||||
wrapper
|
||||
.find(`.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`)
|
||||
.find(
|
||||
`.discussion-filter-container .dropdown-item[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"]`,
|
||||
)
|
||||
.trigger('click');
|
||||
|
||||
expect(filterDiscussion).not.toHaveBeenCalled();
|
||||
|
@ -106,7 +110,7 @@ describe('DiscussionFilter component', () => {
|
|||
|
||||
it('disables commenting when "Show history only" filter is applied', () => {
|
||||
const filterItem = wrapper.find(
|
||||
`.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`,
|
||||
`.discussion-filter-container .dropdown-item[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"]`,
|
||||
);
|
||||
filterItem.trigger('click');
|
||||
|
||||
|
@ -115,7 +119,7 @@ describe('DiscussionFilter component', () => {
|
|||
|
||||
it('enables commenting when "Show history only" filter is not applied', () => {
|
||||
const filterItem = wrapper.find(
|
||||
`.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`,
|
||||
`.discussion-filter-container .dropdown-item[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"]`,
|
||||
);
|
||||
filterItem.trigger('click');
|
||||
|
||||
|
@ -124,10 +128,10 @@ describe('DiscussionFilter component', () => {
|
|||
|
||||
it('renders a dropdown divider for the default filter', () => {
|
||||
const defaultFilter = wrapper.findAll(
|
||||
`.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] > *`,
|
||||
`.discussion-filter-container .dropdown-item-wrapper > *`,
|
||||
);
|
||||
|
||||
expect(defaultFilter.at(defaultFilter.length - 1).classes('dropdown-divider')).toBe(true);
|
||||
expect(defaultFilter.at(1).classes('gl-new-dropdown-divider')).toBe(true);
|
||||
});
|
||||
|
||||
describe('Merge request tabs', () => {
|
||||
|
|
|
@ -55,7 +55,7 @@ describe('Sort Discussion component', () => {
|
|||
it('calls the right actions', () => {
|
||||
createComponent();
|
||||
|
||||
wrapper.find('.js-newest-first').trigger('click');
|
||||
wrapper.find('.js-newest-first').vm.$emit('click');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', DESC);
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
|
||||
|
@ -67,7 +67,7 @@ describe('Sort Discussion component', () => {
|
|||
it('shows the "Oldest First" as the dropdown', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find('.js-dropdown-text').text()).toBe('Oldest first');
|
||||
expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Oldest first');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -79,7 +79,7 @@ describe('Sort Discussion component', () => {
|
|||
|
||||
describe('when the dropdown item is clicked', () => {
|
||||
it('calls the right actions', () => {
|
||||
wrapper.find('.js-oldest-first').trigger('click');
|
||||
wrapper.find('.js-oldest-first').vm.$emit('click');
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', ASC);
|
||||
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
|
||||
|
@ -87,13 +87,13 @@ describe('Sort Discussion component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('applies the active class to the correct button in the dropdown', () => {
|
||||
expect(wrapper.find('.js-newest-first').classes()).toContain('is-active');
|
||||
it('sets is-checked to true on the active button in the dropdown', () => {
|
||||
expect(wrapper.find('.js-newest-first').props('isChecked')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the "Newest First" as the dropdown', () => {
|
||||
expect(wrapper.find('.js-dropdown-text').text()).toBe('Newest first');
|
||||
expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Newest first');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,12 +3,15 @@ import { mount } from '@vue/test-utils';
|
|||
import { merge } from 'lodash';
|
||||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import ReleaseEditNewApp from '~/releases/components/app_edit_new.vue';
|
||||
import { release as originalRelease, milestones as originalMilestones } from '../mock_data';
|
||||
import * as commonUtils from '~/lib/utils/common_utils';
|
||||
import { BACK_URL_PARAM } from '~/releases/constants';
|
||||
import AssetLinksForm from '~/releases/components/asset_links_form.vue';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
const originalMilestones = originalRelease.milestones;
|
||||
|
||||
describe('Release edit/new component', () => {
|
||||
let wrapper;
|
||||
let release;
|
||||
|
|
|
@ -2,16 +2,12 @@ import { range as rge } from 'lodash';
|
|||
import Vuex from 'vuex';
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import ReleasesApp from '~/releases/components/app_index.vue';
|
||||
import createStore from '~/releases/stores';
|
||||
import createListModule from '~/releases/stores/modules/list';
|
||||
import api from '~/api';
|
||||
import {
|
||||
pageInfoHeadersWithoutPagination,
|
||||
pageInfoHeadersWithPagination,
|
||||
release2 as release,
|
||||
releases,
|
||||
} from '../mock_data';
|
||||
import { pageInfoHeadersWithoutPagination, pageInfoHeadersWithPagination } from '../mock_data';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import ReleasesPagination from '~/releases/components/releases_pagination.vue';
|
||||
|
||||
|
@ -25,6 +21,9 @@ jest.mock('~/lib/utils/common_utils', () => ({
|
|||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
const release = getJSONFixture('api/releases/release.json');
|
||||
const releases = [release];
|
||||
|
||||
describe('Releases App ', () => {
|
||||
let wrapper;
|
||||
let fetchReleaseSpy;
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import Vuex from 'vuex';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import ReleaseShowApp from '~/releases/components/app_show.vue';
|
||||
import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
|
||||
import { release as originalRelease } from '../mock_data';
|
||||
import ReleaseBlock from '~/releases/components/release_block.vue';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
|
||||
describe('Release show component', () => {
|
||||
let wrapper;
|
||||
let release;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Vuex from 'vuex';
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import AssetLinksForm from '~/releases/components/asset_links_form.vue';
|
||||
import { release as originalRelease } from '../mock_data';
|
||||
import * as commonUtils from '~/lib/utils/common_utils';
|
||||
import { ENTER_KEY } from '~/lib/utils/keys';
|
||||
import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants';
|
||||
|
@ -9,6 +9,8 @@ import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants';
|
|||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
|
||||
describe('Release edit component', () => {
|
||||
let wrapper;
|
||||
let release;
|
||||
|
@ -223,10 +225,18 @@ describe('Release edit component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('selects the default asset type if no type was provided by the backend', () => {
|
||||
const selected = wrapper.find({ ref: 'typeSelect' }).element.value;
|
||||
describe('when no link type was provided by the backend', () => {
|
||||
beforeEach(() => {
|
||||
delete release.assets.links[0].linkType;
|
||||
|
||||
expect(selected).toBe(DEFAULT_ASSET_LINK_TYPE);
|
||||
factory({ release });
|
||||
});
|
||||
|
||||
it('selects the default asset type', () => {
|
||||
const selected = wrapper.find({ ref: 'typeSelect' }).element.value;
|
||||
|
||||
expect(selected).toBe(DEFAULT_ASSET_LINK_TYPE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlLink, GlIcon } from '@gitlab/ui';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import { truncateSha } from '~/lib/utils/text_utility';
|
||||
import { release as originalRelease } from '../mock_data';
|
||||
import EvidenceBlock from '~/releases/components/evidence_block.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
|
||||
describe('Evidence Block', () => {
|
||||
let wrapper;
|
||||
let release;
|
||||
|
@ -35,7 +37,7 @@ describe('Evidence Block', () => {
|
|||
});
|
||||
|
||||
it('renders the title for the dowload link', () => {
|
||||
expect(wrapper.find(GlLink).text()).toBe('v1.1.2-evidences-1.json');
|
||||
expect(wrapper.find(GlLink).text()).toBe(`v1.1-evidences-1.json`);
|
||||
});
|
||||
|
||||
it('renders the correct hover text for the download', () => {
|
||||
|
@ -43,7 +45,7 @@ describe('Evidence Block', () => {
|
|||
});
|
||||
|
||||
it('renders the correct file link for download', () => {
|
||||
expect(wrapper.find(GlLink).attributes().download).toBe('v1.1.2-evidences-1.json');
|
||||
expect(wrapper.find(GlLink).attributes().download).toBe(`v1.1-evidences-1.json`);
|
||||
});
|
||||
|
||||
describe('sha text', () => {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlCollapse } from '@gitlab/ui';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import ReleaseBlockAssets from '~/releases/components/release_block_assets.vue';
|
||||
import { ASSET_LINK_TYPE } from '~/releases/constants';
|
||||
import { assets } from '../mock_data';
|
||||
|
||||
const { assets } = getJSONFixture('api/releases/release.json');
|
||||
|
||||
describe('Release block assets', () => {
|
||||
let wrapper;
|
||||
|
@ -31,7 +33,7 @@ describe('Release block assets', () => {
|
|||
wrapper.findAll('h5').filter(h5 => h5.text() === sections[type]);
|
||||
|
||||
beforeEach(() => {
|
||||
defaultProps = { assets: cloneDeep(assets) };
|
||||
defaultProps = { assets: convertObjectPropsToCamelCase(assets, { deep: true }) };
|
||||
});
|
||||
|
||||
describe('with default props', () => {
|
||||
|
@ -43,7 +45,7 @@ describe('Release block assets', () => {
|
|||
const accordionButton = findAccordionButton();
|
||||
|
||||
expect(accordionButton.exists()).toBe(true);
|
||||
expect(trimText(accordionButton.text())).toBe('Assets 5');
|
||||
expect(trimText(accordionButton.text())).toBe('Assets 8');
|
||||
});
|
||||
|
||||
it('renders the accordion as expanded by default', () => {
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlLink, GlIcon } from '@gitlab/ui';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue';
|
||||
import { release as originalRelease } from '../mock_data';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
|
||||
const mockFutureDate = new Date(9999, 0, 0).toISOString();
|
||||
let mockIsFutureRelease = false;
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { merge } from 'lodash';
|
||||
import { GlLink } from '@gitlab/ui';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import ReleaseBlockHeader from '~/releases/components/release_block_header.vue';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import { release as originalRelease } from '../mock_data';
|
||||
import { BACK_URL_PARAM } from '~/releases/constants';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
|
||||
describe('Release block header', () => {
|
||||
let wrapper;
|
||||
let release;
|
||||
|
@ -49,7 +51,7 @@ describe('Release block header', () => {
|
|||
});
|
||||
|
||||
it('renders the title as text', () => {
|
||||
expect(findHeader().text()).toBe(release.name);
|
||||
expect(findHeader().text()).toContain(release.name);
|
||||
expect(findHeaderLink().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import ReleaseBlockMetadata from '~/releases/components/release_block_metadata.vue';
|
||||
import { release as originalRelease } from '../mock_data';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
|
||||
const mockFutureDate = new Date(9999, 0, 0).toISOString();
|
||||
let mockIsFutureRelease = false;
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui';
|
||||
import { trimText } from 'helpers/text_helper';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import ReleaseBlockMilestoneInfo from '~/releases/components/release_block_milestone_info.vue';
|
||||
import { milestones as originalMilestones } from '../mock_data';
|
||||
import { MAX_MILESTONES_TO_DISPLAY } from '~/releases/constants';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
|
||||
const { milestones: originalMilestones } = getJSONFixture('api/releases/release.json');
|
||||
|
||||
describe('Release block milestone info', () => {
|
||||
let wrapper;
|
||||
let milestones;
|
||||
|
@ -35,7 +37,7 @@ describe('Release block milestone info', () => {
|
|||
beforeEach(() => factory({ milestones }));
|
||||
|
||||
it('renders the correct percentage', () => {
|
||||
expect(milestoneProgressBarContainer().text()).toContain('41% complete');
|
||||
expect(milestoneProgressBarContainer().text()).toContain('44% complete');
|
||||
});
|
||||
|
||||
it('renders a progress bar that displays the correct percentage', () => {
|
||||
|
@ -44,14 +46,24 @@ describe('Release block milestone info', () => {
|
|||
expect(progressBar.exists()).toBe(true);
|
||||
expect(progressBar.attributes()).toEqual(
|
||||
expect.objectContaining({
|
||||
value: '22',
|
||||
max: '54',
|
||||
value: '4',
|
||||
max: '9',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('renders a list of links to all associated milestones', () => {
|
||||
expect(trimText(milestoneListContainer().text())).toContain('Milestones 13.6 • 13.5');
|
||||
// The API currently returns the milestones in a non-deterministic order,
|
||||
// which causes the frontend fixture used by this test to return the
|
||||
// milestones in one order locally and a different order in the CI pipeline.
|
||||
// This is a bug and is tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/259012
|
||||
// When this bug is fixed this expectation should be updated to
|
||||
// assert the expected order.
|
||||
const containerText = trimText(milestoneListContainer().text());
|
||||
expect(
|
||||
containerText.includes('Milestones 12.4 • 12.3') ||
|
||||
containerText.includes('Milestones 12.3 • 12.4'),
|
||||
).toBe(true);
|
||||
|
||||
milestones.forEach((m, i) => {
|
||||
const milestoneLink = milestoneListContainer()
|
||||
|
@ -65,7 +77,7 @@ describe('Release block milestone info', () => {
|
|||
});
|
||||
|
||||
it('renders the "Issues" section with a total count of issues associated to the milestone(s)', () => {
|
||||
const totalIssueCount = 54;
|
||||
const totalIssueCount = 9;
|
||||
const issuesContainerText = trimText(issuesContainer().text());
|
||||
|
||||
expect(issuesContainerText).toContain(`Issues ${totalIssueCount}`);
|
||||
|
@ -73,7 +85,7 @@ describe('Release block milestone info', () => {
|
|||
const badge = issuesContainer().find(GlBadge);
|
||||
expect(badge.text()).toBe(totalIssueCount.toString());
|
||||
|
||||
expect(issuesContainerText).toContain('Open: 32 • Closed: 22');
|
||||
expect(issuesContainerText).toContain('Open: 5 • Closed: 4');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import $ from 'jquery';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import EvidenceBlock from '~/releases/components/evidence_block.vue';
|
||||
import ReleaseBlock from '~/releases/components/release_block.vue';
|
||||
import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import { release as originalRelease } from '../mock_data';
|
||||
import * as commonUtils from '~/lib/utils/common_utils';
|
||||
import { BACK_URL_PARAM } from '~/releases/constants';
|
||||
import * as urlUtility from '~/lib/utils/url_utility';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
|
||||
describe('Release block', () => {
|
||||
let wrapper;
|
||||
let release;
|
||||
|
@ -46,7 +48,7 @@ describe('Release block', () => {
|
|||
beforeEach(() => factory(release));
|
||||
|
||||
it("renders the block with an id equal to the release's tag name", () => {
|
||||
expect(wrapper.attributes().id).toBe('v0.3');
|
||||
expect(wrapper.attributes().id).toBe(release.tagName);
|
||||
});
|
||||
|
||||
it(`renders an edit button that links to the "Edit release" page with a "${BACK_URL_PARAM}" parameter`, () => {
|
||||
|
@ -107,7 +109,7 @@ describe('Release block', () => {
|
|||
});
|
||||
|
||||
it('does not render external label when link is not external', () => {
|
||||
expect(wrapper.find('.js-assets-list li:nth-child(2) a').text()).not.toContain(
|
||||
expect(wrapper.find('.js-assets-list li:nth-child(3) a').text()).not.toContain(
|
||||
'external source',
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,139 +1,3 @@
|
|||
import { ASSET_LINK_TYPE } from '~/releases/constants';
|
||||
|
||||
export const milestones = [
|
||||
{
|
||||
id: 50,
|
||||
iid: 2,
|
||||
project_id: 18,
|
||||
title: '13.6',
|
||||
description: 'The 13.6 milestone!',
|
||||
state: 'active',
|
||||
created_at: '2019-08-27T17:22:38.280Z',
|
||||
updated_at: '2019-08-27T17:22:38.280Z',
|
||||
due_date: '2019-09-19',
|
||||
start_date: '2019-08-31',
|
||||
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/2',
|
||||
issue_stats: {
|
||||
total: 33,
|
||||
closed: 19,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 49,
|
||||
iid: 1,
|
||||
project_id: 18,
|
||||
title: '13.5',
|
||||
description: 'The 13.5 milestone!',
|
||||
state: 'active',
|
||||
created_at: '2019-08-26T17:55:48.643Z',
|
||||
updated_at: '2019-08-26T17:55:48.643Z',
|
||||
due_date: '2019-10-11',
|
||||
start_date: '2019-08-19',
|
||||
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/1',
|
||||
issue_stats: {
|
||||
total: 21,
|
||||
closed: 3,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const release = {
|
||||
name: 'New release',
|
||||
tag_name: 'v0.3',
|
||||
tag_path: '/root/release-test/-/tags/v0.3',
|
||||
description: 'A super nice release!',
|
||||
description_html: '<p data-sourcepos="1:1-1:21" dir="auto">A super nice release!</p>',
|
||||
created_at: '2019-08-26T17:54:04.952Z',
|
||||
released_at: '2019-08-26T17:54:04.807Z',
|
||||
author: {
|
||||
id: 1,
|
||||
name: 'Administrator',
|
||||
username: 'root',
|
||||
state: 'active',
|
||||
avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
|
||||
web_url: 'http://0.0.0.0:3001/root',
|
||||
},
|
||||
commit: {
|
||||
id: 'c22b0728d1b465f82898c884d32b01aa642f96c1',
|
||||
short_id: 'c22b0728',
|
||||
created_at: '2019-08-26T17:47:07.000Z',
|
||||
parent_ids: [],
|
||||
title: 'Initial commit',
|
||||
message: 'Initial commit',
|
||||
author_name: 'Administrator',
|
||||
author_email: 'admin@example.com',
|
||||
authored_date: '2019-08-26T17:47:07.000Z',
|
||||
committer_name: 'Administrator',
|
||||
committer_email: 'admin@example.com',
|
||||
committed_date: '2019-08-26T17:47:07.000Z',
|
||||
},
|
||||
commit_path: '/root/release-test/commit/c22b0728d1b465f82898c884d32b01aa642f96c1',
|
||||
upcoming_release: false,
|
||||
milestones,
|
||||
evidences: [
|
||||
{
|
||||
filepath:
|
||||
'https://20592.qa-tunnel.gitlab.info/root/test-deployments/-/releases/v1.1.2/evidences/1.json',
|
||||
sha: 'fb3a125fd69a0e5048ebfb0ba43eb32ce4911520dd8d',
|
||||
collected_at: '2018-10-19 15:43:20 +0200',
|
||||
},
|
||||
{
|
||||
filepath:
|
||||
'https://20592.qa-tunnel.gitlab.info/root/test-deployments/-/releases/v1.1.2/evidences/2.json',
|
||||
sha: '6ebd17a66e6a861175735416e49cf677678029805712dd71bb805c609e2d9108',
|
||||
collected_at: '2018-10-19 15:43:20 +0200',
|
||||
},
|
||||
{
|
||||
filepath:
|
||||
'https://20592.qa-tunnel.gitlab.info/root/test-deployments/-/releases/v1.1.2/evidences/3.json',
|
||||
sha: '2f65beaf275c3cb4b4e24fb01d481cc475d69c957830833f15338384816b5cba',
|
||||
collected_at: '2018-10-19 15:43:20 +0200',
|
||||
},
|
||||
],
|
||||
assets: {
|
||||
count: 5,
|
||||
sources: [
|
||||
{
|
||||
format: 'zip',
|
||||
url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.zip',
|
||||
},
|
||||
{
|
||||
format: 'tar.gz',
|
||||
url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.gz',
|
||||
},
|
||||
{
|
||||
format: 'tar.bz2',
|
||||
url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.bz2',
|
||||
},
|
||||
{
|
||||
format: 'tar',
|
||||
url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar',
|
||||
},
|
||||
],
|
||||
links: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'my link',
|
||||
url: 'https://google.com',
|
||||
direct_asset_url: 'https://redirected.google.com',
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'my second link',
|
||||
url:
|
||||
'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
|
||||
direct_asset_url: 'https://redirected.google.com',
|
||||
external: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
_links: {
|
||||
self: 'http://0.0.0.0:3001/root/release-test/-/releases/v0.3',
|
||||
edit_url: 'http://0.0.0.0:3001/root/release-test/-/releases/v0.3/edit',
|
||||
},
|
||||
};
|
||||
|
||||
export const pageInfoHeadersWithoutPagination = {
|
||||
'X-NEXT-PAGE': '',
|
||||
'X-PAGE': '1',
|
||||
|
@ -152,77 +16,6 @@ export const pageInfoHeadersWithPagination = {
|
|||
'X-TOTAL-PAGES': '2',
|
||||
};
|
||||
|
||||
export const assets = {
|
||||
count: 5,
|
||||
sources: [
|
||||
{
|
||||
format: 'zip',
|
||||
url: 'https://example.gitlab.com/path/to/zip',
|
||||
},
|
||||
],
|
||||
links: [
|
||||
{
|
||||
linkType: ASSET_LINK_TYPE.IMAGE,
|
||||
url: 'https://example.gitlab.com/path/to/image',
|
||||
directAssetUrl: 'https://example.gitlab.com/path/to/image',
|
||||
name: 'Example image link',
|
||||
},
|
||||
{
|
||||
linkType: ASSET_LINK_TYPE.PACKAGE,
|
||||
url: 'https://example.gitlab.com/path/to/package',
|
||||
directAssetUrl: 'https://example.gitlab.com/path/to/package',
|
||||
name: 'Example package link',
|
||||
},
|
||||
{
|
||||
linkType: ASSET_LINK_TYPE.RUNBOOK,
|
||||
url: 'https://example.gitlab.com/path/to/runbook',
|
||||
directAssetUrl: 'https://example.gitlab.com/path/to/runbook',
|
||||
name: 'Example runbook link',
|
||||
},
|
||||
{
|
||||
linkType: ASSET_LINK_TYPE.OTHER,
|
||||
url: 'https://example.gitlab.com/path/to/link',
|
||||
directAssetUrl: 'https://example.gitlab.com/path/to/link',
|
||||
name: 'Example link',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const release2 = {
|
||||
name: 'Bionic Beaver',
|
||||
tag_name: '18.04',
|
||||
description: '## changelog\n\n* line 1\n* line2',
|
||||
description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
|
||||
author_name: 'Release bot',
|
||||
author_email: 'release-bot@example.com',
|
||||
created_at: '2012-05-28T05:00:00-07:00',
|
||||
commit: {
|
||||
id: '2695effb5807a22ff3d138d593fd856244e155e7',
|
||||
short_id: '2695effb',
|
||||
title: 'Initial commit',
|
||||
created_at: '2017-07-26T11:08:53.000+02:00',
|
||||
parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'],
|
||||
message: 'Initial commit',
|
||||
author: {
|
||||
avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
|
||||
id: 482476,
|
||||
name: 'John Doe',
|
||||
path: '/johndoe',
|
||||
state: 'active',
|
||||
status_tooltip_html: null,
|
||||
username: 'johndoe',
|
||||
web_url: 'https://gitlab.com/johndoe',
|
||||
},
|
||||
authored_date: '2012-05-28T04:42:42-07:00',
|
||||
committer_name: 'Jack Smith',
|
||||
committer_email: 'jack@example.com',
|
||||
committed_date: '2012-05-28T04:42:42-07:00',
|
||||
},
|
||||
assets,
|
||||
};
|
||||
|
||||
export const releases = [release, release2];
|
||||
|
||||
export const graphqlReleasesResponse = {
|
||||
data: {
|
||||
project: {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import testAction from 'helpers/vuex_action_helper';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import * as actions from '~/releases/stores/modules/detail/actions';
|
||||
import * as types from '~/releases/stores/modules/detail/mutation_types';
|
||||
import { release as originalRelease } from '../../../mock_data';
|
||||
import createState from '~/releases/stores/modules/detail/state';
|
||||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
|
@ -21,6 +21,8 @@ jest.mock('~/lib/utils/url_utility', () => ({
|
|||
joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths,
|
||||
}));
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
|
||||
describe('Release detail actions', () => {
|
||||
let state;
|
||||
let release;
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import createState from '~/releases/stores/modules/detail/state';
|
||||
import mutations from '~/releases/stores/modules/detail/mutations';
|
||||
import * as types from '~/releases/stores/modules/detail/mutation_types';
|
||||
import { release as originalRelease } from '../../../mock_data';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
|
||||
describe('Release detail mutations', () => {
|
||||
let state;
|
||||
let release;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { cloneDeep } from 'lodash';
|
||||
import testAction from 'helpers/vuex_action_helper';
|
||||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import {
|
||||
fetchReleases,
|
||||
fetchReleasesGraphQl,
|
||||
|
@ -17,12 +18,14 @@ import {
|
|||
} from '~/lib/utils/common_utils';
|
||||
import {
|
||||
pageInfoHeadersWithoutPagination,
|
||||
releases as originalReleases,
|
||||
graphqlReleasesResponse as originalGraphqlReleasesResponse,
|
||||
} from '../../../mock_data';
|
||||
import allReleasesQuery from '~/releases/queries/all_releases.query.graphql';
|
||||
import { PAGE_SIZE } from '~/releases/constants';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
const originalReleases = [originalRelease];
|
||||
|
||||
describe('Releases State actions', () => {
|
||||
let mockedState;
|
||||
let releases;
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
import { getJSONFixture } from 'helpers/fixtures';
|
||||
import createState from '~/releases/stores/modules/list/state';
|
||||
import mutations from '~/releases/stores/modules/list/mutations';
|
||||
import * as types from '~/releases/stores/modules/list/mutation_types';
|
||||
import { parseIntPagination } from '~/lib/utils/common_utils';
|
||||
import {
|
||||
pageInfoHeadersWithoutPagination,
|
||||
releases,
|
||||
graphqlReleasesResponse,
|
||||
} from '../../../mock_data';
|
||||
import { parseIntPagination, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import { pageInfoHeadersWithoutPagination, graphqlReleasesResponse } from '../../../mock_data';
|
||||
import { convertGraphQLResponse } from '~/releases/util';
|
||||
|
||||
const originalRelease = getJSONFixture('api/releases/release.json');
|
||||
const originalReleases = [originalRelease];
|
||||
|
||||
describe('Releases Store Mutations', () => {
|
||||
let stateCopy;
|
||||
let restPageInfo;
|
||||
let graphQlPageInfo;
|
||||
let releases;
|
||||
|
||||
beforeEach(() => {
|
||||
stateCopy = createState({});
|
||||
restPageInfo = parseIntPagination(pageInfoHeadersWithoutPagination);
|
||||
graphQlPageInfo = convertGraphQLResponse(graphqlReleasesResponse).paginationInfo;
|
||||
releases = convertObjectPropsToCamelCase(originalReleases, { deep: true });
|
||||
});
|
||||
|
||||
describe('REQUEST_RELEASES', () => {
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import DroplabDropdownButton from '~/vue_shared/components/droplab_dropdown_button.vue';
|
||||
|
||||
const mockActions = [
|
||||
{
|
||||
title: 'Foo',
|
||||
description: 'Some foo action',
|
||||
},
|
||||
{
|
||||
title: 'Bar',
|
||||
description: 'Some bar action',
|
||||
},
|
||||
];
|
||||
|
||||
const createComponent = ({
|
||||
size = '',
|
||||
dropdownClass = '',
|
||||
actions = mockActions,
|
||||
defaultAction = 0,
|
||||
}) =>
|
||||
mount(DroplabDropdownButton, {
|
||||
propsData: {
|
||||
size,
|
||||
dropdownClass,
|
||||
actions,
|
||||
defaultAction,
|
||||
},
|
||||
});
|
||||
|
||||
describe('DroplabDropdownButton', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('data', () => {
|
||||
it('contains `selectedAction` representing value of `defaultAction` prop', () => {
|
||||
expect(wrapper.vm.selectedAction).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('selectedActionTitle', () => {
|
||||
it('returns string containing title of selected action', () => {
|
||||
wrapper.setData({ selectedAction: 0 });
|
||||
|
||||
expect(wrapper.vm.selectedActionTitle).toBe(mockActions[0].title);
|
||||
|
||||
wrapper.setData({ selectedAction: 1 });
|
||||
|
||||
expect(wrapper.vm.selectedActionTitle).toBe(mockActions[1].title);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buttonSizeClass', () => {
|
||||
it('returns string containing button sizing class based on `size` prop', done => {
|
||||
const wrapperWithSize = createComponent({
|
||||
size: 'sm',
|
||||
});
|
||||
|
||||
wrapperWithSize.vm.$nextTick(() => {
|
||||
expect(wrapperWithSize.vm.buttonSizeClass).toBe('btn-sm');
|
||||
|
||||
done();
|
||||
wrapperWithSize.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('handlePrimaryActionClick', () => {
|
||||
it('emits `onActionClick` event on component with selectedAction object as param', () => {
|
||||
jest.spyOn(wrapper.vm, '$emit');
|
||||
|
||||
wrapper.setData({ selectedAction: 0 });
|
||||
wrapper.vm.handlePrimaryActionClick();
|
||||
|
||||
expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionClick', mockActions[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleActionClick', () => {
|
||||
it('emits `onActionSelect` event on component with selectedAction index as param', () => {
|
||||
jest.spyOn(wrapper.vm, '$emit');
|
||||
|
||||
wrapper.vm.handleActionClick(1);
|
||||
|
||||
expect(wrapper.vm.$emit).toHaveBeenCalledWith('onActionSelect', 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders default action button', () => {
|
||||
const defaultButton = wrapper.findAll('.btn').at(0);
|
||||
|
||||
expect(defaultButton.text()).toBe(mockActions[0].title);
|
||||
});
|
||||
|
||||
it('renders dropdown button', () => {
|
||||
const dropdownButton = wrapper.findAll('.dropdown-toggle').at(0);
|
||||
|
||||
expect(dropdownButton.isVisible()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders dropdown actions', () => {
|
||||
const dropdownActions = wrapper.findAll('.dropdown-menu li button');
|
||||
|
||||
Array(dropdownActions.length)
|
||||
.fill()
|
||||
.forEach((_, index) => {
|
||||
const actionContent = dropdownActions.at(index).find('.description');
|
||||
|
||||
expect(actionContent.find('strong').text()).toBe(mockActions[index].title);
|
||||
expect(actionContent.find('p').text()).toBe(mockActions[index].description);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders divider between dropdown actions', () => {
|
||||
const dropdownDivider = wrapper.find('.dropdown-menu .divider');
|
||||
|
||||
expect(dropdownDivider.isVisible()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::BoardResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
let(:dummy_gid) { 'gid://gitlab/Board/1' }
|
||||
|
||||
shared_examples_for 'group and project boards resolver' do
|
||||
it 'does not create a default board' do
|
||||
expect(resolve_board(id: dummy_gid)).to eq nil
|
||||
end
|
||||
|
||||
it 'calls Boards::ListService' do
|
||||
expect_next_instance_of(Boards::ListService) do |service|
|
||||
expect(service).to receive(:execute).and_return([])
|
||||
end
|
||||
|
||||
resolve_board(id: dummy_gid)
|
||||
end
|
||||
|
||||
it 'requires an ID' do
|
||||
expect do
|
||||
resolve(described_class, obj: board_parent, args: {}, ctx: { current_user: user })
|
||||
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
|
||||
end
|
||||
|
||||
context 'when querying for a single board' do
|
||||
let(:board1) { create(:board, name: 'One', resource_parent: board_parent) }
|
||||
|
||||
it 'returns specified board' do
|
||||
expect(resolve_board(id: global_id_of(board1))).to eq board1
|
||||
end
|
||||
|
||||
it 'returns nil if board not found' do
|
||||
outside_parent = create(board_parent.class.underscore.to_sym) # rubocop:disable Rails/SaveBang
|
||||
outside_board = create(:board, name: 'outside board', resource_parent: outside_parent)
|
||||
|
||||
expect(resolve_board(id: global_id_of(outside_board))).to eq nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
context 'when there is no parent' do
|
||||
let(:board_parent) { nil }
|
||||
|
||||
it 'returns nil if parent is nil' do
|
||||
expect(resolve_board(id: dummy_gid)).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project boards' do
|
||||
let(:board_parent) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) }
|
||||
|
||||
it_behaves_like 'group and project boards resolver'
|
||||
end
|
||||
|
||||
context 'when group boards' do
|
||||
let(:board_parent) { create(:group) }
|
||||
|
||||
it_behaves_like 'group and project boards resolver'
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_board(id:)
|
||||
resolve(described_class, obj: board_parent, args: { id: id }, ctx: { current_user: user })
|
||||
end
|
||||
end
|
|
@ -1238,4 +1238,12 @@ RSpec.describe Issue do
|
|||
expect(issue.allows_reviewers?).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#issue_type_supports?' do
|
||||
let_it_be(:issue) { create(:issue) }
|
||||
|
||||
it 'raises error when feature is invalid' do
|
||||
expect { issue.issue_type_supports?(:unkown_feature) }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ RSpec.describe Operations::FeatureFlag do
|
|||
context 'a version 1 feature flag' do
|
||||
it 'is valid if associated with Operations::FeatureFlagScope models' do
|
||||
project = create(:project)
|
||||
feature_flag = described_class.create({ name: 'test', project: project, version: 1,
|
||||
feature_flag = described_class.create!({ name: 'test', project: project, version: 1,
|
||||
scopes_attributes: [{ environment_scope: '*', active: false }] })
|
||||
|
||||
expect(feature_flag).to be_valid
|
||||
|
@ -29,9 +29,10 @@ RSpec.describe Operations::FeatureFlag do
|
|||
|
||||
it 'is invalid if associated with Operations::FeatureFlags::Strategy models' do
|
||||
project = create(:project)
|
||||
feature_flag = described_class.create({ name: 'test', project: project, version: 1,
|
||||
feature_flag = described_class.new({ name: 'test', project: project, version: 1,
|
||||
strategies_attributes: [{ name: 'default', parameters: {} }] })
|
||||
|
||||
expect(feature_flag.valid?).to eq(false)
|
||||
expect(feature_flag.errors.messages).to eq({
|
||||
version_associations: ["version 1 feature flags may not have strategies"]
|
||||
})
|
||||
|
@ -41,9 +42,10 @@ RSpec.describe Operations::FeatureFlag do
|
|||
context 'a version 2 feature flag' do
|
||||
it 'is invalid if associated with Operations::FeatureFlagScope models' do
|
||||
project = create(:project)
|
||||
feature_flag = described_class.create({ name: 'test', project: project, version: 2,
|
||||
feature_flag = described_class.new({ name: 'test', project: project, version: 2,
|
||||
scopes_attributes: [{ environment_scope: '*', active: false }] })
|
||||
|
||||
expect(feature_flag.valid?).to eq(false)
|
||||
expect(feature_flag.errors.messages).to eq({
|
||||
version_associations: ["version 2 feature flags may not have scopes"]
|
||||
})
|
||||
|
@ -51,7 +53,7 @@ RSpec.describe Operations::FeatureFlag do
|
|||
|
||||
it 'is valid if associated with Operations::FeatureFlags::Strategy models' do
|
||||
project = create(:project)
|
||||
feature_flag = described_class.create({ name: 'test', project: project, version: 2,
|
||||
feature_flag = described_class.create!({ name: 'test', project: project, version: 2,
|
||||
strategies_attributes: [{ name: 'default', parameters: {} }] })
|
||||
|
||||
expect(feature_flag).to be_valid
|
||||
|
@ -71,7 +73,7 @@ RSpec.describe Operations::FeatureFlag do
|
|||
it 'defaults to 1 if unspecified' do
|
||||
project = create(:project)
|
||||
|
||||
feature_flag = described_class.create(name: 'my_flag', project: project, active: true)
|
||||
feature_flag = described_class.create!(name: 'my_flag', project: project, active: true)
|
||||
|
||||
expect(feature_flag).to be_valid
|
||||
expect(feature_flag.version_before_type_cast).to eq(1)
|
||||
|
@ -109,14 +111,14 @@ RSpec.describe Operations::FeatureFlag do
|
|||
|
||||
context 'with a version 1 feature flag' do
|
||||
it 'creates a default scope' do
|
||||
feature_flag = described_class.create({ name: 'test', project: project, scopes_attributes: [], version: 1 })
|
||||
feature_flag = described_class.create!({ name: 'test', project: project, scopes_attributes: [], version: 1 })
|
||||
|
||||
expect(feature_flag.scopes.count).to eq(1)
|
||||
expect(feature_flag.scopes.first.environment_scope).to eq('*')
|
||||
end
|
||||
|
||||
it 'allows specifying the default scope in the parameters' do
|
||||
feature_flag = described_class.create({ name: 'test', project: project,
|
||||
feature_flag = described_class.create!({ name: 'test', project: project,
|
||||
scopes_attributes: [{ environment_scope: '*', active: false },
|
||||
{ environment_scope: 'review/*', active: true }], version: 1 })
|
||||
|
||||
|
@ -127,7 +129,7 @@ RSpec.describe Operations::FeatureFlag do
|
|||
|
||||
context 'with a version 2 feature flag' do
|
||||
it 'does not create a default scope' do
|
||||
feature_flag = described_class.create({ name: 'test', project: project, scopes_attributes: [], version: 2 })
|
||||
feature_flag = described_class.create!({ name: 'test', project: project, scopes_attributes: [], version: 2 })
|
||||
|
||||
expect(feature_flag.scopes).to eq([])
|
||||
end
|
||||
|
|
|
@ -880,4 +880,6 @@ RSpec.describe GroupPolicy do
|
|||
it { is_expected.to be_disallowed(:destroy_package) }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'Self-managed Core resource access tokens'
|
||||
end
|
||||
|
|
|
@ -941,4 +941,6 @@ RSpec.describe ProjectPolicy do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'Self-managed Core resource access tokens'
|
||||
end
|
||||
|
|
|
@ -254,24 +254,6 @@ RSpec.describe Git::WikiPushService, services: true do
|
|||
service.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'the wiki_events_on_git_push feature is disabled' do
|
||||
before do
|
||||
stub_feature_flags(wiki_events_on_git_push: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a no-op push'
|
||||
|
||||
context 'but is enabled for a given container' do
|
||||
before do
|
||||
stub_feature_flags(wiki_events_on_git_push: wiki.container)
|
||||
end
|
||||
|
||||
it 'creates events' do
|
||||
expect { process_changes { write_new_page } }.to change(Event, :count).by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -24,16 +24,6 @@ RSpec.describe ResourceAccessTokens::CreateService do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'fails when flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(resource_access_token: false)
|
||||
end
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject).to be nil
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'fails on gitlab.com' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?) { true }
|
||||
|
@ -181,7 +171,6 @@ RSpec.describe ResourceAccessTokens::CreateService do
|
|||
let_it_be(:resource) { project }
|
||||
|
||||
it_behaves_like 'fails when user does not have the permission to create a Resource Bot'
|
||||
it_behaves_like 'fails when flag is disabled'
|
||||
it_behaves_like 'fails on gitlab.com'
|
||||
|
||||
context 'user with valid permission' do
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'Self-managed Core resource access tokens' do
|
||||
before do
|
||||
allow(::Gitlab).to receive(:com?).and_return(false)
|
||||
end
|
||||
|
||||
context 'with owner' do
|
||||
let(:current_user) { owner }
|
||||
|
||||
it { is_expected.to be_allowed(:admin_resource_access_tokens) }
|
||||
end
|
||||
|
||||
context 'with developer' do
|
||||
let(:current_user) { developer }
|
||||
|
||||
it { is_expected.not_to be_allowed(:admin_resource_access_tokens) }
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'GitLab.com Core resource access tokens' do
|
||||
before do
|
||||
allow(::Gitlab).to receive(:com?).and_return(true)
|
||||
stub_ee_application_setting(should_check_namespace_plan: true)
|
||||
end
|
||||
|
||||
context 'with owner' do
|
||||
let(:current_user) { owner }
|
||||
|
||||
it { is_expected.not_to be_allowed(:admin_resource_access_tokens) }
|
||||
end
|
||||
end
|
|
@ -90,7 +90,7 @@ RSpec.shared_examples 'group and project boards query' do
|
|||
|
||||
it_behaves_like 'a working graphql query' do
|
||||
before do
|
||||
post_graphql(query_single_board, current_user: current_user)
|
||||
post_graphql(query_single_board("id: \"gid://gitlab/Board/1\""), current_user: current_user)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue