Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
704b3dfa40
commit
57f8f3552c
23 changed files with 432 additions and 281 deletions
|
@ -142,6 +142,7 @@ export default {
|
|||
},
|
||||
onReachingListBottom() {
|
||||
if (!this.loadingMore && this.hasNextPage) {
|
||||
this.showCount = true;
|
||||
this.loadNextPage();
|
||||
}
|
||||
},
|
||||
|
@ -150,12 +151,19 @@ export default {
|
|||
},
|
||||
handleDragOnEnd(params) {
|
||||
sortableEnd();
|
||||
const { newIndex, oldIndex, from, to, item } = params;
|
||||
const { oldIndex, from, to, item } = params;
|
||||
let { newIndex } = params;
|
||||
const { itemId, itemIid, itemPath } = item.dataset;
|
||||
const { children } = to;
|
||||
let { children } = to;
|
||||
let moveBeforeId;
|
||||
let moveAfterId;
|
||||
|
||||
children = Array.from(children).filter((card) => card.classList.contains('board-card'));
|
||||
|
||||
if (newIndex > children.length) {
|
||||
newIndex = children.length;
|
||||
}
|
||||
|
||||
const getItemId = (el) => Number(el.dataset.itemId);
|
||||
|
||||
// If item is being moved within the same list
|
||||
|
@ -218,6 +226,7 @@ export default {
|
|||
:data-board="list.id"
|
||||
:data-board-type="list.listType"
|
||||
:class="{ 'bg-danger-100': boardItemsSizeExceedsMax }"
|
||||
draggable=".board-card"
|
||||
class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list"
|
||||
data-testid="tree-root-wrapper"
|
||||
@start="handleDragOnStart"
|
||||
|
@ -232,17 +241,17 @@ export default {
|
|||
:item="item"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
<li v-if="showCount" class="board-list-count gl-text-center" data-issue-id="-1">
|
||||
<gl-loading-icon
|
||||
v-if="loadingMore"
|
||||
:label="$options.i18n.loadingMoreboardItems"
|
||||
data-testid="count-loading-icon"
|
||||
/>
|
||||
<span v-if="showingAllItems">{{ showingAllItemsText }}</span>
|
||||
<gl-intersection-observer v-else @appear="onReachingListBottom">
|
||||
<span>{{ paginatedIssueText }}</span>
|
||||
</gl-intersection-observer>
|
||||
</li>
|
||||
<gl-intersection-observer @appear="onReachingListBottom">
|
||||
<li v-if="showCount" class="board-list-count gl-text-center" data-issue-id="-1">
|
||||
<gl-loading-icon
|
||||
v-if="loadingMore"
|
||||
:label="$options.i18n.loadingMoreboardItems"
|
||||
data-testid="count-loading-icon"
|
||||
/>
|
||||
<span v-if="showingAllItems">{{ showingAllItemsText }}</span>
|
||||
<span v-else>{{ paginatedIssueText }}</span>
|
||||
</li>
|
||||
</gl-intersection-observer>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -69,7 +69,7 @@ export default {
|
|||
class="form-control dropdown-input-field"
|
||||
@input="searchBranches"
|
||||
/>
|
||||
<gl-icon :size="18" name="search" class="ml-3 input-icon" use-deprecated-sizes />
|
||||
<gl-icon name="search" class="gl-ml-5 gl-mt-1 input-icon" />
|
||||
</label>
|
||||
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
|
||||
<gl-loading-icon
|
||||
|
|
|
@ -1,25 +1,18 @@
|
|||
<script>
|
||||
import {
|
||||
GlFilteredSearchToken,
|
||||
GlAvatar,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
import { GlAvatar, GlFilteredSearchSuggestion } from '@gitlab/ui';
|
||||
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
import { DEFAULT_LABEL_ANY, DEBOUNCE_DELAY } from '../constants';
|
||||
import { DEFAULT_LABEL_ANY } from '../constants';
|
||||
|
||||
import BaseToken from './base_token.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlFilteredSearchToken,
|
||||
BaseToken,
|
||||
GlAvatar,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
config: {
|
||||
|
@ -30,45 +23,35 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
authors: this.config.initialAuthors || [],
|
||||
defaultAuthors: this.config.defaultAuthors || [DEFAULT_LABEL_ANY],
|
||||
loading: true,
|
||||
preloadedAuthors: [
|
||||
{
|
||||
id: gon.current_user_id,
|
||||
name: gon.current_user_fullname,
|
||||
username: gon.current_username,
|
||||
avatar_url: gon.current_user_avatar_url,
|
||||
},
|
||||
],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentUser() {
|
||||
return {
|
||||
id: gon.current_user_id,
|
||||
name: gon.current_user_fullname,
|
||||
username: gon.current_username,
|
||||
avatar_url: gon.current_user_avatar_url,
|
||||
};
|
||||
},
|
||||
currentValue() {
|
||||
return this.value.data.toLowerCase();
|
||||
},
|
||||
activeAuthor() {
|
||||
return this.authors.find((author) => author.username.toLowerCase() === this.currentValue);
|
||||
},
|
||||
activeAuthorAvatar() {
|
||||
return this.avatarUrl(this.activeAuthor);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
active: {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (!newValue && !this.authors.length) {
|
||||
this.fetchAuthorBySearchTerm(this.value.data);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getActiveAuthor(authors, currentValue) {
|
||||
return authors.find((author) => author.username.toLowerCase() === currentValue);
|
||||
},
|
||||
getAvatarUrl(author) {
|
||||
return author.avatarUrl || author.avatar_url;
|
||||
},
|
||||
fetchAuthorBySearchTerm(searchTerm) {
|
||||
this.loading = true;
|
||||
const fetchPromise = this.config.fetchPath
|
||||
? this.config.fetchAuthors(this.config.fetchPath, searchTerm)
|
||||
: this.config.fetchAuthors(searchTerm);
|
||||
|
@ -89,69 +72,47 @@ export default {
|
|||
this.loading = false;
|
||||
});
|
||||
},
|
||||
avatarUrl(author) {
|
||||
return author.avatarUrl || author.avatar_url;
|
||||
},
|
||||
searchAuthors: debounce(function debouncedSearch({ data }) {
|
||||
this.fetchAuthorBySearchTerm(data);
|
||||
}, DEBOUNCE_DELAY),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-filtered-search-token
|
||||
:config="config"
|
||||
v-bind="{ ...$props, ...$attrs }"
|
||||
v-on="$listeners"
|
||||
@input="searchAuthors"
|
||||
<base-token
|
||||
:token-config="config"
|
||||
:token-value="value"
|
||||
:token-active="active"
|
||||
:tokens-list-loading="loading"
|
||||
:token-values="authors"
|
||||
:fn-active-token-value="getActiveAuthor"
|
||||
:default-token-values="defaultAuthors"
|
||||
:preloaded-token-values="preloadedAuthors"
|
||||
:recent-token-values-storage-key="config.recentTokenValuesStorageKey"
|
||||
@fetch-token-values="fetchAuthorBySearchTerm"
|
||||
>
|
||||
<template #view="{ inputValue }">
|
||||
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
|
||||
<gl-avatar
|
||||
v-if="activeAuthor"
|
||||
v-if="activeTokenValue"
|
||||
:size="16"
|
||||
:src="activeAuthorAvatar"
|
||||
:src="getAvatarUrl(activeTokenValue)"
|
||||
shape="circle"
|
||||
class="gl-mr-2"
|
||||
/>
|
||||
<span>{{ activeAuthor ? activeAuthor.name : inputValue }}</span>
|
||||
<span>{{ activeTokenValue ? activeTokenValue.name : inputValue }}</span>
|
||||
</template>
|
||||
<template #suggestions>
|
||||
<template #token-values-list="{ tokenValues }">
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="author in defaultAuthors"
|
||||
:key="author.value"
|
||||
:value="author.value"
|
||||
v-for="author in tokenValues"
|
||||
:key="author.username"
|
||||
:value="author.username"
|
||||
>
|
||||
{{ author.text }}
|
||||
<div class="gl-display-flex">
|
||||
<gl-avatar :size="32" :src="getAvatarUrl(author)" />
|
||||
<div>
|
||||
<div>{{ author.name }}</div>
|
||||
<div>@{{ author.username }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</gl-filtered-search-suggestion>
|
||||
<gl-dropdown-divider v-if="defaultAuthors.length" />
|
||||
<template v-if="loading">
|
||||
<gl-filtered-search-suggestion v-if="currentUser.id" :value="currentUser.username">
|
||||
<div class="gl-display-flex">
|
||||
<gl-avatar :size="32" :src="avatarUrl(currentUser)" />
|
||||
<div>
|
||||
<div>{{ currentUser.name }}</div>
|
||||
<div>@{{ currentUser.username }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</gl-filtered-search-suggestion>
|
||||
<gl-loading-icon class="gl-mt-3" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="author in authors"
|
||||
:key="author.username"
|
||||
:value="author.username"
|
||||
>
|
||||
<div class="d-flex">
|
||||
<gl-avatar :size="32" :src="avatarUrl(author)" />
|
||||
<div>
|
||||
<div>{{ author.name }}</div>
|
||||
<div>@{{ author.username }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</gl-filtered-search-suggestion>
|
||||
</template>
|
||||
</template>
|
||||
</gl-filtered-search-token>
|
||||
</base-token>
|
||||
</template>
|
||||
|
|
|
@ -48,6 +48,11 @@ export default {
|
|||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
preloadedTokenValues: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
recentTokenValuesStorageKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -158,6 +163,11 @@ export default {
|
|||
<slot name="token-values-list" :token-values="recentTokenValues"></slot>
|
||||
<gl-dropdown-divider />
|
||||
</template>
|
||||
<slot
|
||||
v-if="preloadedTokenValues.length"
|
||||
name="token-values-list"
|
||||
:token-values="preloadedTokenValues"
|
||||
></slot>
|
||||
<gl-loading-icon v-if="tokensListLoading" />
|
||||
<template v-else>
|
||||
<slot name="token-values-list" :token-values="availableTokenValues"></slot>
|
||||
|
|
|
@ -47,6 +47,14 @@ $top-level-item-color: $purple-900;
|
|||
|
||||
@include context-header-collapsed;
|
||||
|
||||
.context-header {
|
||||
@include gl-h-auto;
|
||||
|
||||
a {
|
||||
@include gl-p-2;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-top-level-items > li {
|
||||
.sidebar-sub-level-items {
|
||||
&:not(.flyout-list) {
|
||||
|
@ -60,17 +68,16 @@ $top-level-item-color: $purple-900;
|
|||
}
|
||||
|
||||
.toggle-sidebar-button {
|
||||
padding: 16px;
|
||||
width: $contextual-sidebar-collapsed-width - 1px;
|
||||
width: $contextual-sidebar-collapsed-width;
|
||||
|
||||
.collapse-text,
|
||||
.icon-chevron-double-lg-left {
|
||||
.collapse-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-chevron-double-lg-right {
|
||||
display: block;
|
||||
margin: 0;
|
||||
.icon-chevron-double-lg-left {
|
||||
@include gl-rotate-180;
|
||||
@include gl-display-block; // TODO: shouldn't be needed after the flag roll out
|
||||
@include gl-m-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,12 +90,13 @@ $top-level-item-color: $purple-900;
|
|||
}
|
||||
|
||||
.badge.badge-pill:not(.fly-out-badge),
|
||||
.nav-item-name {
|
||||
.nav-item-name,
|
||||
.collapse-text {
|
||||
@include gl-sr-only;
|
||||
}
|
||||
|
||||
.sidebar-top-level-items > li > a {
|
||||
min-height: 45px;
|
||||
min-height: unset;
|
||||
}
|
||||
|
||||
.fly-out-top-item {
|
||||
|
@ -98,6 +106,10 @@ $top-level-item-color: $purple-900;
|
|||
.avatar-container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
li.active > a {
|
||||
background-color: $indigo-900-alpha-008;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin sub-level-items-flyout {
|
||||
|
@ -158,6 +170,7 @@ $top-level-item-color: $purple-900;
|
|||
|
||||
@include gl-p-2;
|
||||
@include gl-mb-2;
|
||||
@include gl-mt-0;
|
||||
|
||||
.avatar-container {
|
||||
@include gl-font-weight-normal;
|
||||
|
@ -187,7 +200,6 @@ $top-level-item-color: $purple-900;
|
|||
@include gl-align-items-center;
|
||||
@include gl-rounded-base;
|
||||
@include gl-w-auto;
|
||||
transition: padding $sidebar-transition-duration;
|
||||
margin: $sidebar-top-item-tb-margin $sidebar-top-item-lr-margin;
|
||||
|
||||
&:hover {
|
||||
|
@ -226,22 +238,16 @@ $top-level-item-color: $purple-900;
|
|||
//
|
||||
|
||||
.nav-sidebar {
|
||||
@include gl-fixed;
|
||||
@include gl-bottom-0;
|
||||
@include gl-left-0;
|
||||
transition: width $sidebar-transition-duration, left $sidebar-transition-duration;
|
||||
position: fixed;
|
||||
z-index: 600;
|
||||
width: $contextual-sidebar-width;
|
||||
top: $header-height;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: $gray-50;
|
||||
transform: translate3d(0, 0, 0);
|
||||
|
||||
&:not(.sidebar-collapsed-desktop) {
|
||||
@media (min-width: map-get($grid-breakpoints, sm)) and (max-width: map-get($grid-breakpoints, sm)) {
|
||||
box-shadow: inset -1px 0 0 $border-color, 2px 1px 3px $dropdown-shadow-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.sidebar-collapsed-desktop {
|
||||
@include collapse-contextual-sidebar;
|
||||
}
|
||||
|
@ -380,7 +386,11 @@ $top-level-item-color: $purple-900;
|
|||
.close-nav-button {
|
||||
@include side-panel-toggle;
|
||||
background-color: $gray-50;
|
||||
border-top: 1px solid $border-color;
|
||||
color: $top-level-item-color;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: $contextual-sidebar-width;
|
||||
|
||||
.collapse-text,
|
||||
.icon-chevron-double-lg-left,
|
||||
|
@ -389,22 +399,6 @@ $top-level-item-color: $purple-900;
|
|||
}
|
||||
}
|
||||
|
||||
.toggle-sidebar-button,
|
||||
.close-nav-button {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: $contextual-sidebar-width - 1px;
|
||||
border-top: 1px solid $border-color;
|
||||
|
||||
svg {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.icon-chevron-double-lg-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -9,7 +9,7 @@ $sidebar-transition-duration: 0.3s;
|
|||
$sidebar-breakpoint: 1024px;
|
||||
$default-transition-duration: 0.15s;
|
||||
$contextual-sidebar-width: 220px;
|
||||
$contextual-sidebar-collapsed-width: 50px;
|
||||
$contextual-sidebar-collapsed-width: 48px;
|
||||
$toggle-sidebar-height: 48px;
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,10 +6,6 @@
|
|||
width: 240px;
|
||||
}
|
||||
|
||||
.rule-elapsed-minutes {
|
||||
width: 56px;
|
||||
}
|
||||
|
||||
.rule-close-icon {
|
||||
right: 1rem;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
- avatar_size = sidebar_refactor_disabled? ? 24 : 18
|
||||
- avatar_size_class = sidebar_refactor_disabled? ? 's40' : 's32'
|
||||
|
||||
%aside.nav-sidebar.qa-admin-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), 'aria-label': _('Admin navigation') }
|
||||
.nav-sidebar-inner-scroll
|
||||
.context-header
|
||||
= link_to admin_root_path, title: _('Admin Overview') do
|
||||
%span.avatar-container.rect-avatar.s32.settings-avatar
|
||||
= sprite_icon('admin', size: 18)
|
||||
%span{ class: ['avatar-container', 'settings-avatar', 'rect-avatar', avatar_size_class] }
|
||||
= sprite_icon('admin', size: avatar_size)
|
||||
%span.sidebar-context-title
|
||||
= _('Admin Area')
|
||||
%ul.sidebar-top-level-items{ data: { qa_selector: 'admin_sidebar_overview_submenu_content' } }
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
- avatar_size_class = sidebar_refactor_disabled? ? 's40' : 's32'
|
||||
- avatar_classes = ['avatar-container', 'rect-avatar', 'group-avatar']
|
||||
- avatar_classes << avatar_size_class
|
||||
|
||||
= link_to group_path(@group), title: @group.name do
|
||||
%span.avatar-container.rect-avatar.s32.group-avatar
|
||||
= group_icon(@group, class: "avatar s32 avatar-tile")
|
||||
%span{ class: avatar_classes }
|
||||
= group_icon(@group, class: ['avatar', 'avatar-tile', avatar_size_class])
|
||||
%span.sidebar-context-title
|
||||
= @group.name
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
- avatar_size = sidebar_refactor_disabled? ? 40 : 32
|
||||
- avatar_size_class = sidebar_refactor_disabled? ? 's40' : 's32'
|
||||
|
||||
%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **sidebar_tracking_attributes_by_object(current_user), 'aria-label': _('User settings') }
|
||||
.nav-sidebar-inner-scroll
|
||||
.context-header
|
||||
= link_to profile_path, title: _('Profile Settings') do
|
||||
%span.avatar-container.s32.settings-avatar
|
||||
= image_tag avatar_icon_for_user(current_user, 32), class: "avatar s32 avatar-tile js-sidebar-user-avatar", alt: current_user.name, data: { testid: 'sidebar-user-avatar' }
|
||||
%span{ class: ['avatar-container', 'settings-avatar', avatar_size_class] }
|
||||
= image_tag avatar_icon_for_user(current_user, avatar_size), class: ['avatar', 'avatar-tile', 'js-sidebar-user-avatar', avatar_size_class], alt: current_user.name, data: { testid: 'sidebar-user-avatar' }
|
||||
%span.sidebar-context-title= _('User Settings')
|
||||
%ul.sidebar-top-level-items
|
||||
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
|
||||
|
|
|
@ -22,15 +22,15 @@
|
|||
.account-well.gl-mb-3
|
||||
%ul
|
||||
%li
|
||||
= _('Your Primary Email will be used for avatar detection.')
|
||||
- profile_message = _('Your primary email is used for avatar detection. You can change it in your %{openingTag}profile settings%{closingTag}.') % { openingTag: "<a href='#{profile_path}'>".html_safe, closingTag: '</a>'.html_safe}
|
||||
= profile_message.html_safe
|
||||
%li
|
||||
= _('Your Commit Email will be used for web based operations, such as edits and merges.')
|
||||
= _('Your commit email is used for web based operations, such as edits and merges.')
|
||||
%li
|
||||
- address = profile_notifications_path
|
||||
- notification_message = _('Your Default Notification Email will be used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set.') % { openingTag: "<a href='#{address}'>".html_safe, closingTag: '</a>'.html_safe}
|
||||
- notification_message = _('Your default notification email is used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set.') % { openingTag: "<a href='#{profile_notifications_path}'>".html_safe, closingTag: '</a>'.html_safe}
|
||||
= notification_message.html_safe
|
||||
%li
|
||||
= _('Your Public Email will be displayed on your public profile.')
|
||||
= _('Your public email will be displayed on your public profile.')
|
||||
%li
|
||||
= _('All email addresses will be used to identify your commits.')
|
||||
%ul.content-list
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
%a.toggle-sidebar-button.js-toggle-sidebar.qa-toggle-sidebar.rspec-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
|
||||
= sprite_icon('chevron-double-lg-left', css_class: 'icon-chevron-double-lg-left')
|
||||
= sprite_icon('chevron-double-lg-right', css_class: 'icon-chevron-double-lg-right')
|
||||
%span.collapse-text= _("Collapse sidebar")
|
||||
- if sidebar_refactor_disabled?
|
||||
= sprite_icon('chevron-double-lg-right', css_class: 'icon-chevron-double-lg-right')
|
||||
%span.collapse-text.gl-ml-3= _("Collapse sidebar")
|
||||
|
||||
= button_tag class: 'close-nav-button', type: 'button' do
|
||||
= sprite_icon('close')
|
||||
%span.collapse-text= _("Close sidebar")
|
||||
%span.collapse-text.gl-ml-3= _("Close sidebar")
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
- avatar_size = sidebar_refactor_disabled? ? 40 : 32
|
||||
- avatar_size_class = sidebar_refactor_disabled? ? 's40' : 's32'
|
||||
|
||||
= link_to scope_menu.link, **scope_menu.container_html_options do
|
||||
%span.avatar-container.rect-avatar.s32.project-avatar
|
||||
= source_icon(scope_menu.container, alt: scope_menu.title, class: 'avatar s32 avatar-tile', width: 32, height: 32)
|
||||
%span{ class: ['avatar-container', 'rect-avatar', 'project-avatar', avatar_size_class] }
|
||||
= source_icon(scope_menu.container, alt: scope_menu.title, class: ['avatar', 'avatar-tile', avatar_size_class], width: avatar_size, height: avatar_size)
|
||||
%span.sidebar-context-title
|
||||
= scope_menu.title
|
||||
|
|
|
@ -62,7 +62,7 @@ investigate it for potential threats by
|
|||
|
||||
The **Threat Monitoring** page's **Policy** tab displays deployed
|
||||
network policies for all available environments. You can check a
|
||||
network policy's `yaml` manifest, toggle the policy's enforcement
|
||||
network policy's `yaml` manifest, its enforcement
|
||||
status, and create and edit deployed policies. This section has the
|
||||
following prerequisites:
|
||||
|
||||
|
@ -71,8 +71,7 @@ following prerequisites:
|
|||
|
||||
Network policies are fetched directly from the selected environment's
|
||||
deployment platform. Changes performed outside of this tab are
|
||||
reflected upon refresh. Enforcement status changes are deployed
|
||||
directly to a deployment namespace of the selected environment.
|
||||
reflected upon refresh.
|
||||
|
||||
By default, the network policy list contains predefined policies in a
|
||||
disabled state. Once enabled, a predefined policy deploys to the
|
||||
|
@ -89,8 +88,9 @@ users must make changes by following the
|
|||
To change a network policy's enforcement status:
|
||||
|
||||
- Click the network policy you want to update.
|
||||
- Click the **Enforcement status** toggle to update the selected policy.
|
||||
- Click the **Apply changes** button to deploy network policy changes.
|
||||
- Click the **Edit policy** button.
|
||||
- Click the **Policy status** toggle to update the selected policy.
|
||||
- Click the **Save changes** button to deploy network policy changes.
|
||||
|
||||
Disabled network policies have the `network-policy.gitlab.com/disabled_by: gitlab` selector inside
|
||||
the `podSelector` block. This narrows the scope of such a policy and as a result it doesn't affect
|
||||
|
|
|
@ -74,6 +74,11 @@ module API
|
|||
|
||||
save_current_user_in_env(@current_user) if @current_user
|
||||
|
||||
if @current_user
|
||||
::Gitlab::Database::LoadBalancing::RackMiddleware
|
||||
.stick_or_unstick(env, :user, @current_user.id)
|
||||
end
|
||||
|
||||
@current_user
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
|
|
@ -34,8 +34,15 @@ module API
|
|||
end
|
||||
|
||||
def current_runner
|
||||
token = params[:token]
|
||||
|
||||
if token
|
||||
::Gitlab::Database::LoadBalancing::RackMiddleware
|
||||
.stick_or_unstick(env, :runner, token)
|
||||
end
|
||||
|
||||
strong_memoize(:current_runner) do
|
||||
::Ci::Runner.find_by_token(params[:token].to_s)
|
||||
::Ci::Runner.find_by_token(token.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -65,8 +72,15 @@ module API
|
|||
end
|
||||
|
||||
def current_job
|
||||
id = params[:id]
|
||||
|
||||
if id
|
||||
::Gitlab::Database::LoadBalancing::RackMiddleware
|
||||
.stick_or_unstick(env, :build, id)
|
||||
end
|
||||
|
||||
strong_memoize(:current_job) do
|
||||
::Ci::Build.find_by_id(params[:id])
|
||||
::Ci::Build.find_by_id(id)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4035,9 +4035,6 @@ msgstr ""
|
|||
msgid "Apply a template"
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply changes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Apply suggestion"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13111,6 +13108,9 @@ msgstr ""
|
|||
msgid "EscalationPolicies|Edit escalation policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Elapsed time must be greater than or equal to zero."
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Email on-call user in schedule"
|
||||
msgstr ""
|
||||
|
||||
|
@ -21851,7 +21851,7 @@ msgstr ""
|
|||
msgid "NetworkPolicies|Are you sure you want to delete this policy? This action cannot be undone."
|
||||
msgstr ""
|
||||
|
||||
msgid "NetworkPolicies|Choose whether to enforce this policy."
|
||||
msgid "NetworkPolicies|Container runtime"
|
||||
msgstr ""
|
||||
|
||||
msgid "NetworkPolicies|Create policy"
|
||||
|
@ -37821,12 +37821,6 @@ msgstr ""
|
|||
msgid "Your CSV import for project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Commit Email will be used for web based operations, such as edits and merges."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Default Notification Email will be used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your DevOps Report gives an overview of how you are using GitLab from a feature perspective. View how you compare with other organizations, discover features you are not using, and learn best practices through blog posts and white papers."
|
||||
msgstr ""
|
||||
|
||||
|
@ -37854,18 +37848,12 @@ msgstr ""
|
|||
msgid "Your Personal Access Token was revoked"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Primary Email will be used for avatar detection."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Projects (default)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Projects' Activity"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your Public Email will be displayed on your public profile."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your SSH key has expired"
|
||||
msgstr ""
|
||||
|
||||
|
@ -37953,12 +37941,18 @@ msgstr ""
|
|||
msgid "Your comment will be discarded."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your commit email is used for web based operations, such as edits and merges."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your dashboard has been copied. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your dashboard has been updated. You can %{web_ide_link_start}edit it here%{web_ide_link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your default notification email is used for account notifications if a %{openingTag}group-specific email address%{closingTag} is not set."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your deployment services will be broken, you will need to manually fix the services after renaming."
|
||||
msgstr ""
|
||||
|
||||
|
@ -38043,6 +38037,9 @@ msgstr ""
|
|||
msgid "Your personal access tokens will expire in %{days_to_expire} days or less"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your primary email is used for avatar detection. You can change it in your %{openingTag}profile settings%{closingTag}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your profile"
|
||||
msgstr ""
|
||||
|
||||
|
@ -38052,6 +38049,9 @@ msgstr ""
|
|||
msgid "Your projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your public email will be displayed on your public profile."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your request for access could not be processed: %{error_meesage}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ RSpec.describe 'Issue Boards', :js do
|
|||
end
|
||||
|
||||
it 'moves to bottom of another list' do
|
||||
drag(list_from_index: 1, list_to_index: 2, to_index: 2, duration: 1020)
|
||||
drag(list_from_index: 1, list_to_index: 2, to_index: 3, duration: 1020)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
|
|
|
@ -3,15 +3,12 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects > Wiki > User views wiki in project page' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
sign_in(project.owner)
|
||||
end
|
||||
|
||||
context 'when repository is disabled for project' do
|
||||
let(:project) do
|
||||
let_it_be(:project) do
|
||||
create(:project,
|
||||
:wiki_repo,
|
||||
:repository_disabled,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import {
|
||||
GlFilteredSearchToken,
|
||||
GlFilteredSearchTokenSegment,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
GlAvatar,
|
||||
} from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
|
@ -16,6 +15,7 @@ import {
|
|||
DEFAULT_NONE_ANY,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
|
||||
import { mockAuthorToken, mockAuthors } from '../mock_data';
|
||||
|
||||
|
@ -62,6 +62,8 @@ describe('AuthorToken', () => {
|
|||
let mock;
|
||||
let wrapper;
|
||||
|
||||
const getBaseToken = () => wrapper.findComponent(BaseToken);
|
||||
|
||||
beforeEach(() => {
|
||||
window.gon = {
|
||||
...originalGon,
|
||||
|
@ -79,104 +81,127 @@ describe('AuthorToken', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('currentValue', () => {
|
||||
it('returns lowercase string for `value.data`', () => {
|
||||
wrapper = createComponent({ value: { data: 'FOO' } });
|
||||
|
||||
expect(wrapper.vm.currentValue).toBe('foo');
|
||||
describe('methods', () => {
|
||||
describe('fetchAuthorBySearchTerm', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent();
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeAuthor', () => {
|
||||
it('returns object for currently present `value.data`', async () => {
|
||||
wrapper = createComponent({ value: { data: mockAuthors[0].username } });
|
||||
it('calls `config.fetchAuthors` with provided searchTerm param', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchAuthors');
|
||||
|
||||
wrapper.setData({
|
||||
authors: mockAuthors,
|
||||
});
|
||||
getBaseToken().vm.$emit('fetch-token-values', mockAuthors[0].username);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.activeAuthor).toEqual(mockAuthors[0]);
|
||||
expect(wrapper.vm.config.fetchAuthors).toHaveBeenCalledWith(
|
||||
mockAuthorToken.fetchPath,
|
||||
mockAuthors[0].username,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAuthorBySearchTerm', () => {
|
||||
it('calls `config.fetchAuthors` with provided searchTerm param', () => {
|
||||
wrapper = createComponent();
|
||||
it('sets response to `authors` when request is succesful', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockResolvedValue(mockAuthors);
|
||||
|
||||
jest.spyOn(wrapper.vm.config, 'fetchAuthors');
|
||||
getBaseToken().vm.$emit('fetch-token-values', 'root');
|
||||
|
||||
wrapper.vm.fetchAuthorBySearchTerm(mockAuthors[0].username);
|
||||
|
||||
expect(wrapper.vm.config.fetchAuthors).toHaveBeenCalledWith(
|
||||
mockAuthorToken.fetchPath,
|
||||
mockAuthors[0].username,
|
||||
);
|
||||
});
|
||||
|
||||
it('sets response to `authors` when request is succesful', () => {
|
||||
wrapper = createComponent();
|
||||
|
||||
jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockResolvedValue(mockAuthors);
|
||||
|
||||
wrapper.vm.fetchAuthorBySearchTerm('root');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.authors).toEqual(mockAuthors);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls `createFlash` with flash error message when request fails', () => {
|
||||
wrapper = createComponent();
|
||||
|
||||
jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({});
|
||||
|
||||
wrapper.vm.fetchAuthorBySearchTerm('root');
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: 'There was a problem fetching users.',
|
||||
return waitForPromises().then(() => {
|
||||
expect(getBaseToken().props('tokenValues')).toEqual(mockAuthors);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('sets `loading` to false when request completes', () => {
|
||||
wrapper = createComponent();
|
||||
it('calls `createFlash` with flash error message when request fails', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({});
|
||||
|
||||
jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({});
|
||||
getBaseToken().vm.$emit('fetch-token-values', 'root');
|
||||
|
||||
wrapper.vm.fetchAuthorBySearchTerm('root');
|
||||
return waitForPromises().then(() => {
|
||||
expect(createFlash).toHaveBeenCalledWith({
|
||||
message: 'There was a problem fetching users.',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return waitForPromises().then(() => {
|
||||
expect(wrapper.vm.loading).toBe(false);
|
||||
it('sets `loading` to false when request completes', async () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({});
|
||||
|
||||
getBaseToken().vm.$emit('fetch-token-values', 'root');
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(getBaseToken().props('tokensListLoading')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders gl-filtered-search-token component', () => {
|
||||
wrapper = createComponent({ data: { authors: mockAuthors } });
|
||||
it('renders base-token component', () => {
|
||||
wrapper = createComponent({
|
||||
value: { data: mockAuthors[0].username },
|
||||
data: { authors: mockAuthors },
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true);
|
||||
const baseTokenEl = getBaseToken();
|
||||
|
||||
expect(baseTokenEl.exists()).toBe(true);
|
||||
expect(baseTokenEl.props()).toMatchObject({
|
||||
tokenValues: mockAuthors,
|
||||
fnActiveTokenValue: wrapper.vm.getActiveAuthor,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders token item when value is selected', () => {
|
||||
wrapper = createComponent({
|
||||
value: { data: mockAuthors[0].username },
|
||||
data: { authors: mockAuthors },
|
||||
stubs: { Portal: true },
|
||||
});
|
||||
|
||||
return wrapper.vm.$nextTick(() => {
|
||||
const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment);
|
||||
|
||||
expect(tokenSegments).toHaveLength(3); // Author, =, "Administrator"
|
||||
expect(tokenSegments.at(2).text()).toBe(mockAuthors[0].name); // "Administrator"
|
||||
|
||||
const tokenValue = tokenSegments.at(2);
|
||||
|
||||
expect(tokenValue.findComponent(GlAvatar).props('src')).toBe(mockAuthors[0].avatar_url);
|
||||
expect(tokenValue.text()).toBe(mockAuthors[0].name); // "Administrator"
|
||||
});
|
||||
});
|
||||
|
||||
it('renders token value with correct avatarUrl from author object', async () => {
|
||||
const getAvatarEl = () =>
|
||||
wrapper.findAll(GlFilteredSearchTokenSegment).at(2).findComponent(GlAvatar);
|
||||
|
||||
wrapper = createComponent({
|
||||
value: { data: mockAuthors[0].username },
|
||||
data: {
|
||||
authors: [
|
||||
{
|
||||
...mockAuthors[0],
|
||||
},
|
||||
],
|
||||
},
|
||||
stubs: { Portal: true },
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(getAvatarEl().props('src')).toBe(mockAuthors[0].avatar_url);
|
||||
|
||||
wrapper.setData({
|
||||
authors: [
|
||||
{
|
||||
...mockAuthors[0],
|
||||
avatarUrl: mockAuthors[0].avatar_url,
|
||||
avatar_url: undefined,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(getAvatarEl().props('src')).toBe(mockAuthors[0].avatar_url);
|
||||
});
|
||||
|
||||
it('renders provided defaultAuthors as suggestions', async () => {
|
||||
const defaultAuthors = DEFAULT_NONE_ANY;
|
||||
wrapper = createComponent({
|
||||
|
@ -237,10 +262,6 @@ describe('AuthorToken', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('shows loading icon', () => {
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('shows current user', () => {
|
||||
const firstSuggestion = wrapper.findComponent(GlFilteredSearchSuggestion).text();
|
||||
expect(firstSuggestion).toContain('Administrator');
|
||||
|
|
69
spec/lib/api/helpers/runner_spec.rb
Normal file
69
spec/lib/api/helpers/runner_spec.rb
Normal file
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::Helpers::Runner do
|
||||
let(:helper) { Class.new { include API::Helpers::Runner }.new }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:env).and_return({})
|
||||
end
|
||||
|
||||
describe '#current_job' do
|
||||
let(:build) { create(:ci_build, :running) }
|
||||
|
||||
it 'handles sticking of a build when a build ID is specified' do
|
||||
allow(helper).to receive(:params).and_return(id: build.id)
|
||||
|
||||
expect(Gitlab::Database::LoadBalancing::RackMiddleware)
|
||||
.to receive(:stick_or_unstick)
|
||||
.with({}, :build, build.id)
|
||||
|
||||
helper.current_job
|
||||
end
|
||||
|
||||
it 'does not handle sticking if no build ID was specified' do
|
||||
allow(helper).to receive(:params).and_return({})
|
||||
|
||||
expect(Gitlab::Database::LoadBalancing::RackMiddleware)
|
||||
.not_to receive(:stick_or_unstick)
|
||||
|
||||
helper.current_job
|
||||
end
|
||||
|
||||
it 'returns the build if one could be found' do
|
||||
allow(helper).to receive(:params).and_return(id: build.id)
|
||||
|
||||
expect(helper.current_job).to eq(build)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#current_runner' do
|
||||
let(:runner) { create(:ci_runner, token: 'foo') }
|
||||
|
||||
it 'handles sticking of a runner if a token is specified' do
|
||||
allow(helper).to receive(:params).and_return(token: runner.token)
|
||||
|
||||
expect(Gitlab::Database::LoadBalancing::RackMiddleware)
|
||||
.to receive(:stick_or_unstick)
|
||||
.with({}, :runner, runner.token)
|
||||
|
||||
helper.current_runner
|
||||
end
|
||||
|
||||
it 'does not handle sticking if no token was specified' do
|
||||
allow(helper).to receive(:params).and_return({})
|
||||
|
||||
expect(Gitlab::Database::LoadBalancing::RackMiddleware)
|
||||
.not_to receive(:stick_or_unstick)
|
||||
|
||||
helper.current_runner
|
||||
end
|
||||
|
||||
it 'returns the runner if one could be found' do
|
||||
allow(helper).to receive(:params).and_return(token: runner.token)
|
||||
|
||||
expect(helper.current_runner).to eq(runner)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,6 +7,66 @@ RSpec.describe API::Helpers do
|
|||
|
||||
subject { Class.new.include(described_class).new }
|
||||
|
||||
describe '#current_user' do
|
||||
include Rack::Test::Methods
|
||||
|
||||
let(:user) { build(:user, id: 42) }
|
||||
|
||||
let(:helper) do
|
||||
Class.new(Grape::API::Instance) do
|
||||
helpers API::APIGuard::HelperMethods
|
||||
helpers API::Helpers
|
||||
format :json
|
||||
|
||||
get 'user' do
|
||||
current_user ? { id: current_user.id } : { found: false }
|
||||
end
|
||||
|
||||
get 'protected' do
|
||||
authenticate_by_gitlab_geo_node_token!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def app
|
||||
helper
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab::Database::LoadBalancing).to receive(:enable?).and_return(true)
|
||||
end
|
||||
|
||||
it 'handles sticking when a user could be found' do
|
||||
allow_any_instance_of(API::Helpers).to receive(:initial_current_user).and_return(user)
|
||||
|
||||
expect(Gitlab::Database::LoadBalancing::RackMiddleware)
|
||||
.to receive(:stick_or_unstick).with(any_args, :user, 42)
|
||||
|
||||
get 'user'
|
||||
|
||||
expect(Gitlab::Json.parse(last_response.body)).to eq({ 'id' => user.id })
|
||||
end
|
||||
|
||||
it 'does not handle sticking if no user could be found' do
|
||||
allow_any_instance_of(API::Helpers).to receive(:initial_current_user).and_return(nil)
|
||||
|
||||
expect(Gitlab::Database::LoadBalancing::RackMiddleware)
|
||||
.not_to receive(:stick_or_unstick)
|
||||
|
||||
get 'user'
|
||||
|
||||
expect(Gitlab::Json.parse(last_response.body)).to eq({ 'found' => false })
|
||||
end
|
||||
|
||||
it 'returns the user if one could be found' do
|
||||
allow_any_instance_of(API::Helpers).to receive(:initial_current_user).and_return(user)
|
||||
|
||||
get 'user'
|
||||
|
||||
expect(Gitlab::Json.parse(last_response.body)).to eq({ 'id' => user.id })
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_project' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
|
|
|
@ -16,8 +16,19 @@ RSpec.describe API::Wikis do
|
|||
include WorkhorseHelpers
|
||||
include AfterNextHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group).tap { |g| g.add_owner(user) } }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group).tap { |g| g.add_owner(user) } }
|
||||
let_it_be(:group_project) { create(:project, :wiki_repo, namespace: group) }
|
||||
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:maintainer) { create(:user) }
|
||||
let_it_be(:project_wiki_disabled) do
|
||||
create(:project, :wiki_repo, :wiki_disabled).tap do |project|
|
||||
project.add_developer(developer)
|
||||
project.add_maintainer(maintainer)
|
||||
end
|
||||
end
|
||||
|
||||
let(:project_wiki) { create(:project_wiki, project: project, user: user) }
|
||||
let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
|
||||
let(:expected_keys_with_content) { %w(content format slug title) }
|
||||
|
@ -32,7 +43,7 @@ RSpec.describe API::Wikis do
|
|||
let(:url) { "/projects/#{project.id}/wikis" }
|
||||
|
||||
context 'when wiki is disabled' do
|
||||
let(:project) { create(:project, :wiki_repo, :wiki_disabled) }
|
||||
let(:project) { project_wiki_disabled }
|
||||
|
||||
context 'when user is guest' do
|
||||
before do
|
||||
|
@ -44,9 +55,7 @@ RSpec.describe API::Wikis do
|
|||
|
||||
context 'when user is developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
||||
get api(url, user)
|
||||
get api(url, developer)
|
||||
end
|
||||
|
||||
include_examples 'wiki API 403 Forbidden'
|
||||
|
@ -54,9 +63,7 @@ RSpec.describe API::Wikis do
|
|||
|
||||
context 'when user is maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
||||
get api(url, user)
|
||||
get api(url, maintainer)
|
||||
end
|
||||
|
||||
include_examples 'wiki API 403 Forbidden'
|
||||
|
@ -125,7 +132,7 @@ RSpec.describe API::Wikis do
|
|||
let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" }
|
||||
|
||||
context 'when wiki is disabled' do
|
||||
let(:project) { create(:project, :wiki_repo, :wiki_disabled) }
|
||||
let(:project) { project_wiki_disabled }
|
||||
|
||||
context 'when user is guest' do
|
||||
before do
|
||||
|
@ -137,9 +144,7 @@ RSpec.describe API::Wikis do
|
|||
|
||||
context 'when user is developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
|
||||
get api(url, user)
|
||||
get api(url, developer)
|
||||
end
|
||||
|
||||
include_examples 'wiki API 403 Forbidden'
|
||||
|
@ -147,9 +152,7 @@ RSpec.describe API::Wikis do
|
|||
|
||||
context 'when user is maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
||||
get api(url, user)
|
||||
get api(url, maintainer)
|
||||
end
|
||||
|
||||
include_examples 'wiki API 403 Forbidden'
|
||||
|
@ -249,7 +252,7 @@ RSpec.describe API::Wikis do
|
|||
let(:url) { "/projects/#{project.id}/wikis" }
|
||||
|
||||
context 'when wiki is disabled' do
|
||||
let(:project) { create(:project, :wiki_disabled, :wiki_repo) }
|
||||
let(:project) { project_wiki_disabled }
|
||||
|
||||
context 'when user is guest' do
|
||||
before do
|
||||
|
@ -261,8 +264,7 @@ RSpec.describe API::Wikis do
|
|||
|
||||
context 'when user is developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
post(api(url, user), params: payload)
|
||||
post(api(url, developer), params: payload)
|
||||
end
|
||||
|
||||
include_examples 'wiki API 403 Forbidden'
|
||||
|
@ -270,8 +272,7 @@ RSpec.describe API::Wikis do
|
|||
|
||||
context 'when user is maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
post(api(url, user), params: payload)
|
||||
post(api(url, maintainer), params: payload)
|
||||
end
|
||||
|
||||
include_examples 'wiki API 403 Forbidden'
|
||||
|
@ -469,7 +470,7 @@ RSpec.describe API::Wikis do
|
|||
end
|
||||
|
||||
context 'when wiki belongs to a group project' do
|
||||
let(:project) { create(:project, :wiki_repo, namespace: group) }
|
||||
let(:project) { group_project }
|
||||
|
||||
include_examples 'wikis API updates wiki page'
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue