Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c172bb9967
commit
b296ffa543
|
@ -10,7 +10,6 @@ import {
|
|||
GlTabs,
|
||||
GlTab,
|
||||
GlButton,
|
||||
GlTable,
|
||||
} from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import alertQuery from '../graphql/queries/details.query.graphql';
|
||||
|
@ -28,6 +27,7 @@ import { toggleContainerClasses } from '~/lib/utils/dom_utils';
|
|||
import SystemNote from './system_notes/system_note.vue';
|
||||
import AlertSidebar from './alert_sidebar.vue';
|
||||
import AlertMetrics from './alert_metrics.vue';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
|
||||
const containerEl = document.querySelector('.page-with-contextual-sidebar');
|
||||
|
||||
|
@ -55,6 +55,7 @@ export default {
|
|||
},
|
||||
],
|
||||
components: {
|
||||
AlertDetailsTable,
|
||||
GlBadge,
|
||||
GlAlert,
|
||||
GlIcon,
|
||||
|
@ -63,7 +64,6 @@ export default {
|
|||
GlTab,
|
||||
GlTabs,
|
||||
GlButton,
|
||||
GlTable,
|
||||
TimeAgoTooltip,
|
||||
AlertSidebar,
|
||||
SystemNote,
|
||||
|
@ -331,20 +331,7 @@ export default {
|
|||
</div>
|
||||
<div class="gl-pl-2" data-testid="runbook">{{ alert.runbook }}</div>
|
||||
</div>
|
||||
<gl-table
|
||||
class="alert-management-details-table"
|
||||
:items="[{ 'Full Alert Payload': 'Value', ...alert }]"
|
||||
:show-empty="true"
|
||||
:busy="loading"
|
||||
stacked
|
||||
>
|
||||
<template #empty>
|
||||
{{ s__('AlertManagement|No alert data to display.') }}
|
||||
</template>
|
||||
<template #table-busy>
|
||||
<gl-loading-icon size="lg" color="dark" class="mt-3" />
|
||||
</template>
|
||||
</gl-table>
|
||||
<alert-details-table :alert="alert" :loading="loading" />
|
||||
</gl-tab>
|
||||
<gl-tab :data-testid="$options.tabsConfig[1].id" :title="$options.tabsConfig[1].title">
|
||||
<alert-metrics :dashboard-url="alert.metricsDashboardUrl" />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import $ from 'jquery';
|
||||
import './autosize';
|
||||
import './bind_in_out';
|
||||
import './markdown/render_gfm';
|
||||
import initGFMInput from './markdown/gfm_auto_complete';
|
||||
import initCopyAsGFM from './markdown/copy_as_gfm';
|
||||
import initCopyToClipboard from './copy_to_clipboard';
|
||||
import './details_behavior';
|
||||
|
@ -15,9 +15,27 @@ import initCollapseSidebarOnWindowResize from './collapse_sidebar_on_window_resi
|
|||
import initSelect2Dropdowns from './select2';
|
||||
|
||||
installGlEmojiElement();
|
||||
initGFMInput();
|
||||
|
||||
initCopyAsGFM();
|
||||
initCopyToClipboard();
|
||||
|
||||
initPageShortcuts();
|
||||
initCollapseSidebarOnWindowResize();
|
||||
initSelect2Dropdowns();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.requestIdleCallback(
|
||||
() => {
|
||||
// Check if we have to Load GFM Input
|
||||
const $gfmInputs = $('.js-gfm-input:not(.js-gfm-input-initialized)');
|
||||
if ($gfmInputs.length) {
|
||||
import(/* webpackChunkName: 'initGFMInput' */ './markdown/gfm_auto_complete')
|
||||
.then(({ default: initGFMInput }) => {
|
||||
initGFMInput($gfmInputs);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
{ timeout: 500 },
|
||||
);
|
||||
});
|
||||
|
|
|
@ -2,8 +2,8 @@ import $ from 'jquery';
|
|||
import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
|
||||
export default function initGFMInput() {
|
||||
$('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => {
|
||||
export default function initGFMInput($els) {
|
||||
$els.each((i, el) => {
|
||||
const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
|
||||
const enableGFM = parseBoolean(el.dataset.supportsAutocomplete);
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
/* eslint-disable vue/no-v-html */
|
||||
import { GlPopover, GlSprintf, GlButton } from '@gitlab/ui';
|
||||
import { parseBoolean, scrollToElement, setCookie, getCookie } from '~/lib/utils/common_utils';
|
||||
import { s__ } from '~/locale';
|
||||
|
@ -114,7 +113,7 @@ export default {
|
|||
:css-classes="['suggest-gitlab-ci-yml', 'ml-4']"
|
||||
>
|
||||
<template #title>
|
||||
<span v-html="suggestTitle"></span>
|
||||
<span>{{ suggestTitle }}</span>
|
||||
<span class="ml-auto">
|
||||
<gl-button
|
||||
:aria-label="__('Close')"
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { getMilestone } from 'ee_else_ce/boards/boards_util';
|
||||
import ListIssue from 'ee_else_ce/boards/models/issue';
|
||||
import eventHub from '../eventhub';
|
||||
import ProjectSelect from './project_select.vue';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
|
||||
export default {
|
||||
name: 'BoardNewIssue',
|
||||
|
@ -13,6 +15,7 @@ export default {
|
|||
ProjectSelect,
|
||||
GlButton,
|
||||
},
|
||||
mixins: [glFeatureFlagMixin()],
|
||||
props: {
|
||||
groupId: {
|
||||
type: Number,
|
||||
|
@ -32,6 +35,7 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isSwimlanesOn']),
|
||||
disabled() {
|
||||
if (this.groupId) {
|
||||
return this.title === '' || !this.selectedProject.name;
|
||||
|
@ -44,6 +48,7 @@ export default {
|
|||
eventHub.$on('setSelectedProject', this.setSelectedProject);
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['addListIssue', 'addListIssueFailure']),
|
||||
submit(e) {
|
||||
e.preventDefault();
|
||||
if (this.title.trim() === '') return Promise.resolve();
|
||||
|
@ -70,21 +75,31 @@ export default {
|
|||
eventHub.$emit(`scroll-board-list-${this.list.id}`);
|
||||
this.cancel();
|
||||
|
||||
if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn) {
|
||||
this.addListIssue({ list: this.list, issue, position: 0 });
|
||||
}
|
||||
|
||||
return this.list
|
||||
.newIssue(issue)
|
||||
.then(() => {
|
||||
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||
$(this.$refs.submitButton).enable();
|
||||
|
||||
if (!this.glFeatures.boardsWithSwimlanes || !this.isSwimlanesOn) {
|
||||
boardsStore.setIssueDetail(issue);
|
||||
boardsStore.setListDetail(this.list);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||
$(this.$refs.submitButton).enable();
|
||||
|
||||
// Remove the issue
|
||||
if (this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn) {
|
||||
this.addListIssueFailure({ list: this.list, issue });
|
||||
} else {
|
||||
this.list.removeIssue(issue);
|
||||
}
|
||||
|
||||
// Show error message
|
||||
this.error = true;
|
||||
|
|
|
@ -235,6 +235,14 @@ export default {
|
|||
notImplemented();
|
||||
},
|
||||
|
||||
addListIssue: ({ commit }, { list, issue, position }) => {
|
||||
commit(types.ADD_ISSUE_TO_LIST, { list, issue, position });
|
||||
},
|
||||
|
||||
addListIssueFailure: ({ commit }, { list, issue }) => {
|
||||
commit(types.ADD_ISSUE_TO_LIST_FAILURE, { list, issue });
|
||||
},
|
||||
|
||||
fetchBacklog: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
import { __ } from '~/locale';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import eventHub from '../eventhub';
|
||||
import { ListType } from '../constants';
|
||||
import IssueProject from '../models/project';
|
||||
|
@ -303,7 +304,7 @@ const boardsStore = {
|
|||
onNewListIssueResponse(list, issue, data) {
|
||||
issue.refreshData(data);
|
||||
|
||||
if (list.issuesSize > 1) {
|
||||
if (!gon.features.boardsWithSwimlanes && list.issuesSize > 1) {
|
||||
const moveBeforeId = list.issues[1].id;
|
||||
this.moveIssue(issue.id, null, null, null, moveBeforeId);
|
||||
}
|
||||
|
@ -710,6 +711,10 @@ const boardsStore = {
|
|||
},
|
||||
|
||||
newIssue(id, issue) {
|
||||
if (typeof id === 'string') {
|
||||
id = getIdFromGraphQLId(id);
|
||||
}
|
||||
|
||||
return axios.post(this.generateIssuesPath(id), {
|
||||
issue,
|
||||
});
|
||||
|
|
|
@ -24,6 +24,8 @@ export const RECEIVE_MOVE_ISSUE_ERROR = 'RECEIVE_MOVE_ISSUE_ERROR';
|
|||
export const REQUEST_UPDATE_ISSUE = 'REQUEST_UPDATE_ISSUE';
|
||||
export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS';
|
||||
export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR';
|
||||
export const ADD_ISSUE_TO_LIST = 'ADD_ISSUE_TO_LIST';
|
||||
export const ADD_ISSUE_TO_LIST_FAILURE = 'ADD_ISSUE_TO_LIST_FAILURE';
|
||||
export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
|
||||
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
|
||||
export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import { sortBy } from 'lodash';
|
||||
import { sortBy, pull } from 'lodash';
|
||||
import * as mutationTypes from './mutation_types';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
|
@ -8,6 +8,10 @@ const notImplemented = () => {
|
|||
throw new Error('Not implemented!');
|
||||
};
|
||||
|
||||
const removeIssueFromList = (state, listId, issueId) => {
|
||||
Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId));
|
||||
};
|
||||
|
||||
export default {
|
||||
[mutationTypes.SET_INITIAL_BOARD_DATA](state, data) {
|
||||
const { boardType, disabled, showPromotion, ...endpoints } = data;
|
||||
|
@ -131,6 +135,18 @@ export default {
|
|||
notImplemented();
|
||||
},
|
||||
|
||||
[mutationTypes.ADD_ISSUE_TO_LIST]: (state, { list, issue, position }) => {
|
||||
const listIssues = state.issuesByListId[list.id];
|
||||
listIssues.splice(position, 0, issue.id);
|
||||
Vue.set(state.issuesByListId, list.id, listIssues);
|
||||
Vue.set(state.issues, issue.id, issue);
|
||||
},
|
||||
|
||||
[mutationTypes.ADD_ISSUE_TO_LIST_FAILURE]: (state, { list, issue }) => {
|
||||
state.error = __('An error occurred while creating the issue. Please try again.');
|
||||
removeIssueFromList(state, list.id, issue.id);
|
||||
},
|
||||
|
||||
[mutationTypes.SET_CURRENT_PAGE]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { ApolloMutation } from 'vue-apollo';
|
||||
import { GlTooltipDirective, GlIcon, GlLoadingIcon, GlLink } from '@gitlab/ui';
|
||||
import { GlTooltipDirective, GlIcon, GlLoadingIcon, GlLink, GlBadge } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import createFlash from '~/flash';
|
||||
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
|
||||
|
@ -27,6 +27,7 @@ export default {
|
|||
GlLink,
|
||||
ToggleRepliesWidget,
|
||||
TimeAgoTooltip,
|
||||
GlBadge,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -148,14 +149,14 @@ export default {
|
|||
}
|
||||
},
|
||||
onCreateNoteError(err) {
|
||||
this.$emit('createNoteError', err);
|
||||
this.$emit('create-note-error', err);
|
||||
},
|
||||
hideForm() {
|
||||
this.isFormRendered = false;
|
||||
this.discussionComment = '';
|
||||
},
|
||||
showForm() {
|
||||
this.$emit('openForm', this.discussion.id);
|
||||
this.$emit('open-form', this.discussion.id);
|
||||
this.isFormRendered = true;
|
||||
},
|
||||
toggleResolvedStatus() {
|
||||
|
@ -167,11 +168,11 @@ export default {
|
|||
})
|
||||
.then(({ data }) => {
|
||||
if (data.errors?.length > 0) {
|
||||
this.$emit('resolveDiscussionError', data.errors[0]);
|
||||
this.$emit('resolve-discussion-error', data.errors[0]);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.$emit('resolveDiscussionError', err);
|
||||
this.$emit('resolve-discussion-error', err);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isResolving = false;
|
||||
|
@ -192,13 +193,12 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="design-discussion-wrapper">
|
||||
<div
|
||||
class="badge badge-pill gl-display-flex gl-align-items-center gl-justify-content-center"
|
||||
<gl-badge
|
||||
class="gl-display-flex gl-align-items-center gl-justify-content-center gl-cursor-pointer"
|
||||
:class="{ resolved: discussion.resolved }"
|
||||
type="button"
|
||||
>
|
||||
{{ discussion.index }}
|
||||
</div>
|
||||
</gl-badge>
|
||||
<ul
|
||||
class="design-discussion bordered-box gl-relative gl-p-0 gl-list-style-none"
|
||||
data-qa-selector="design_discussion_content"
|
||||
|
@ -208,7 +208,7 @@ export default {
|
|||
:markdown-preview-path="markdownPreviewPath"
|
||||
:is-resolving="isResolving"
|
||||
:class="{ 'gl-bg-blue-50': isDiscussionActive }"
|
||||
@error="$emit('updateNoteError', $event)"
|
||||
@error="$emit('update-note-error', $event)"
|
||||
>
|
||||
<template v-if="discussion.resolvable" #resolveDiscussion>
|
||||
<button
|
||||
|
@ -216,7 +216,6 @@ export default {
|
|||
:class="{ 'is-active': discussion.resolved }"
|
||||
:title="resolveCheckboxText"
|
||||
:aria-label="resolveCheckboxText"
|
||||
type="button"
|
||||
class="line-resolve-btn note-action-button gl-mr-3"
|
||||
data-testid="resolve-button"
|
||||
@click.stop="toggleResolvedStatus"
|
||||
|
@ -252,7 +251,7 @@ export default {
|
|||
:markdown-preview-path="markdownPreviewPath"
|
||||
:is-resolving="isResolving"
|
||||
:class="{ 'gl-bg-blue-50': isDiscussionActive }"
|
||||
@error="$emit('updateNoteError', $event)"
|
||||
@error="$emit('update-note-error', $event)"
|
||||
/>
|
||||
<li v-show="isReplyPlaceholderVisible" class="reply-wrapper">
|
||||
<reply-placeholder
|
||||
|
@ -275,8 +274,8 @@ export default {
|
|||
v-model="discussionComment"
|
||||
:is-saving="loading"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
@submitForm="mutate"
|
||||
@cancelForm="hideForm"
|
||||
@submit-form="mutate"
|
||||
@cancel-form="hideForm"
|
||||
>
|
||||
<template v-if="discussion.resolvable" #resolveCheckbox>
|
||||
<label data-testid="resolve-checkbox">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
/* eslint-disable vue/no-v-html */
|
||||
import { ApolloMutation } from 'vue-apollo';
|
||||
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
||||
import { GlTooltipDirective, GlIcon, GlLink } from '@gitlab/ui';
|
||||
import updateNoteMutation from '../../graphql/mutations/update_note.mutation.graphql';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
|
||||
|
@ -18,6 +18,7 @@ export default {
|
|||
DesignReplyForm,
|
||||
ApolloMutation,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
|
@ -83,27 +84,27 @@ export default {
|
|||
:img-alt="author.username"
|
||||
:img-size="40"
|
||||
/>
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="gl-display-flex gl-justify-content-space-between">
|
||||
<div>
|
||||
<a
|
||||
<gl-link
|
||||
v-once
|
||||
:href="author.webUrl"
|
||||
class="js-user-link"
|
||||
:data-user-id="author.id"
|
||||
:data-username="author.username"
|
||||
>
|
||||
<span class="note-header-author-name bold">{{ author.name }}</span>
|
||||
<span class="note-header-author-name gl-font-weight-bold">{{ author.name }}</span>
|
||||
<span v-if="author.status_tooltip_html" v-html="author.status_tooltip_html"></span>
|
||||
<span class="note-headline-light">@{{ author.username }}</span>
|
||||
</a>
|
||||
</gl-link>
|
||||
<span class="note-headline-light note-headline-meta">
|
||||
<span class="system-note-message"> <slot></slot> </span>
|
||||
<a
|
||||
<gl-link
|
||||
class="note-timestamp system-note-separator gl-display-block gl-mb-2"
|
||||
:href="`#note_${noteAnchorId}`"
|
||||
>
|
||||
<time-ago-tooltip :time="note.createdAt" tooltip-placement="bottom" />
|
||||
</a>
|
||||
</gl-link>
|
||||
</span>
|
||||
</div>
|
||||
<div class="gl-display-flex">
|
||||
|
@ -122,7 +123,7 @@ export default {
|
|||
</div>
|
||||
<template v-if="!isEditing">
|
||||
<div
|
||||
class="note-text js-note-text md"
|
||||
class="note-text js-note-text"
|
||||
data-qa-selector="note_content"
|
||||
v-html="note.bodyHtml"
|
||||
></div>
|
||||
|
@ -143,9 +144,9 @@ export default {
|
|||
:is-saving="loading"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
:is-new-comment="false"
|
||||
class="mt-5"
|
||||
@submitForm="mutate"
|
||||
@cancelForm="hideForm"
|
||||
class="gl-mt-5"
|
||||
@submit-form="mutate"
|
||||
@cancel-form="hideForm"
|
||||
/>
|
||||
</apollo-mutation>
|
||||
</timeline-entry-item>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlDeprecatedButton, GlModal } from '@gitlab/ui';
|
||||
import { GlButton, GlModal } from '@gitlab/ui';
|
||||
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
|
@ -7,7 +7,7 @@ export default {
|
|||
name: 'DesignReplyForm',
|
||||
components: {
|
||||
MarkdownField,
|
||||
GlDeprecatedButton,
|
||||
GlButton,
|
||||
GlModal,
|
||||
},
|
||||
props: {
|
||||
|
@ -66,13 +66,13 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
submitForm() {
|
||||
if (this.hasValue) this.$emit('submitForm');
|
||||
if (this.hasValue) this.$emit('submit-form');
|
||||
},
|
||||
cancelComment() {
|
||||
if (this.hasValue && this.formText !== this.value) {
|
||||
this.$refs.cancelCommentModal.show();
|
||||
} else {
|
||||
this.$emit('cancelForm');
|
||||
this.$emit('cancel-form');
|
||||
}
|
||||
},
|
||||
focusInput() {
|
||||
|
@ -112,20 +112,21 @@ export default {
|
|||
</markdown-field>
|
||||
<slot name="resolveCheckbox"></slot>
|
||||
<div class="note-form-actions gl-display-flex gl-justify-content-space-between">
|
||||
<gl-deprecated-button
|
||||
<gl-button
|
||||
ref="submitButton"
|
||||
:disabled="!hasValue || isSaving"
|
||||
category="primary"
|
||||
variant="success"
|
||||
type="submit"
|
||||
data-track-event="click_button"
|
||||
data-qa-selector="save_comment_button"
|
||||
@click="$emit('submitForm')"
|
||||
@click="$emit('submit-form')"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</gl-deprecated-button>
|
||||
<gl-deprecated-button ref="cancelButton" @click="cancelComment">{{
|
||||
</gl-button>
|
||||
<gl-button ref="cancelButton" variant="default" category="primary" @click="cancelComment">{{
|
||||
__('Cancel')
|
||||
}}</gl-deprecated-button>
|
||||
}}</gl-button>
|
||||
</div>
|
||||
<gl-modal
|
||||
ref="cancelCommentModal"
|
||||
|
@ -134,7 +135,7 @@ export default {
|
|||
:ok-title="modalSettings.okTitle"
|
||||
:cancel-title="modalSettings.cancelTitle"
|
||||
modal-id="cancel-comment-modal"
|
||||
@ok="$emit('cancelForm')"
|
||||
@ok="$emit('cancel-form')"
|
||||
>{{ modalSettings.content }}
|
||||
</gl-modal>
|
||||
</form>
|
||||
|
|
|
@ -159,11 +159,11 @@ export default {
|
|||
:resolved-discussions-expanded="resolvedDiscussionsExpanded"
|
||||
:discussion-with-open-form="discussionWithOpenForm"
|
||||
data-testid="unresolved-discussion"
|
||||
@createNoteError="$emit('onDesignDiscussionError', $event)"
|
||||
@updateNoteError="$emit('updateNoteError', $event)"
|
||||
@resolveDiscussionError="$emit('resolveDiscussionError', $event)"
|
||||
@create-note-error="$emit('onDesignDiscussionError', $event)"
|
||||
@update-note-error="$emit('updateNoteError', $event)"
|
||||
@resolve-discussion-error="$emit('resolveDiscussionError', $event)"
|
||||
@click.native.stop="updateActiveDiscussion(discussion.notes[0].id)"
|
||||
@openForm="updateDiscussionWithOpenForm"
|
||||
@open-form="updateDiscussionWithOpenForm"
|
||||
/>
|
||||
<template v-if="resolvedDiscussions.length > 0">
|
||||
<gl-button
|
||||
|
|
|
@ -372,8 +372,8 @@ export default {
|
|||
v-model="comment"
|
||||
:is-saving="loading"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
@submitForm="mutate"
|
||||
@cancelForm="closeCommentForm"
|
||||
@submit-form="mutate"
|
||||
@cancel-form="closeCommentForm"
|
||||
/> </apollo-mutation
|
||||
></template>
|
||||
</design-sidebar>
|
||||
|
|
|
@ -71,12 +71,15 @@ class GfmAutoComplete {
|
|||
setupLifecycle() {
|
||||
this.input.each((i, input) => {
|
||||
const $input = $(input);
|
||||
if (!$input.hasClass('js-gfm-input-initialized')) {
|
||||
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
|
||||
$input.on('change.atwho', () => input.dispatchEvent(new Event('input')));
|
||||
// This triggers at.js again
|
||||
// Needed for quick actions with suffixes (ex: /label ~)
|
||||
$input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
|
||||
$input.on('clear-commands-cache.atwho', () => this.clearCache());
|
||||
$input.addClass('js-gfm-input-initialized');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,6 @@ import $ from 'jquery';
|
|||
import Vue from 'vue';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import { highCountTrim } from '~/lib/utils/text_utility';
|
||||
import SetStatusModalTrigger from './set_status_modal/set_status_modal_trigger.vue';
|
||||
import SetStatusModalWrapper from './set_status_modal/set_status_modal_wrapper.vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import Tracking from '~/tracking';
|
||||
|
||||
/**
|
||||
|
@ -26,35 +23,24 @@ export default function initTodoToggle() {
|
|||
|
||||
function initStatusTriggers() {
|
||||
const setStatusModalTriggerEl = document.querySelector('.js-set-status-modal-trigger');
|
||||
const setStatusModalWrapperEl = document.querySelector('.js-set-status-modal-wrapper');
|
||||
|
||||
if (setStatusModalTriggerEl || setStatusModalWrapperEl) {
|
||||
if (setStatusModalTriggerEl) {
|
||||
setStatusModalTriggerEl.addEventListener('click', () => {
|
||||
import(
|
||||
/* webpackChunkName: 'statusModalBundle' */ './set_status_modal/set_status_modal_wrapper.vue'
|
||||
)
|
||||
.then(({ default: SetStatusModalWrapper }) => {
|
||||
const setStatusModalWrapperEl = document.querySelector('.js-set-status-modal-wrapper');
|
||||
const statusModalElement = document.createElement('div');
|
||||
setStatusModalWrapperEl.appendChild(statusModalElement);
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: setStatusModalTriggerEl,
|
||||
el: statusModalElement,
|
||||
data() {
|
||||
const { hasStatus } = this.$options.el.dataset;
|
||||
|
||||
return {
|
||||
hasStatus: parseBoolean(hasStatus),
|
||||
};
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(SetStatusModalTrigger, {
|
||||
props: {
|
||||
hasStatus: this.hasStatus,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: setStatusModalWrapperEl,
|
||||
data() {
|
||||
const { currentEmoji, currentMessage } = this.$options.el.dataset;
|
||||
const { currentEmoji, currentMessage } = setStatusModalWrapperEl.dataset;
|
||||
|
||||
return {
|
||||
currentEmoji,
|
||||
|
@ -72,6 +58,9 @@ function initStatusTriggers() {
|
|||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,5 +90,5 @@ export function initNavUserDropdownTracking() {
|
|||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
requestIdleCallback(initStatusTriggers);
|
||||
initNavUserDropdownTracking();
|
||||
requestIdleCallback(initNavUserDropdownTracking);
|
||||
});
|
||||
|
|
|
@ -44,6 +44,7 @@ export default {
|
|||
:aria-label="s__('IDE|Edit')"
|
||||
data-container="body"
|
||||
data-placement="right"
|
||||
data-qa-selector="edit_mode_tab"
|
||||
type="button"
|
||||
class="ide-sidebar-link js-ide-edit-mode"
|
||||
@click.prevent="changedActivityView($event, $options.leftSidebarViews.edit.name)"
|
||||
|
@ -78,8 +79,9 @@ export default {
|
|||
:aria-label="s__('IDE|Commit')"
|
||||
data-container="body"
|
||||
data-placement="right"
|
||||
data-qa-selector="commit_mode_tab"
|
||||
type="button"
|
||||
class="ide-sidebar-link js-ide-commit-mode qa-commit-mode-tab"
|
||||
class="ide-sidebar-link js-ide-commit-mode"
|
||||
@click.prevent="changedActivityView($event, $options.leftSidebarViews.commit.name)"
|
||||
>
|
||||
<gl-icon name="commit" />
|
||||
|
|
|
@ -103,6 +103,7 @@ export default {
|
|||
:title="lastCommit.message"
|
||||
:href="getCommitPath(lastCommit.short_id)"
|
||||
class="commit-sha"
|
||||
data-qa-selector="commit_sha_content"
|
||||
>{{ lastCommit.short_id }}</a
|
||||
>
|
||||
by
|
||||
|
|
|
@ -31,7 +31,6 @@ import initLogoAnimation from './logo';
|
|||
import initFrequentItemDropdowns from './frequent_items';
|
||||
import initBreadcrumbs from './breadcrumb';
|
||||
import initUsagePingConsent from './usage_ping_consent';
|
||||
import initPerformanceBar from './performance_bar';
|
||||
import initSearchAutocomplete from './search_autocomplete';
|
||||
import GlFieldErrors from './gl_field_errors';
|
||||
import initUserPopovers from './user_popovers';
|
||||
|
@ -164,8 +163,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
const $document = $(document);
|
||||
const bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
|
||||
if (document.querySelector('#js-peek')) initPerformanceBar({ container: '#js-peek' });
|
||||
|
||||
initUserTracking();
|
||||
initLayoutNav();
|
||||
initAlertHandler();
|
||||
|
|
|
@ -380,7 +380,7 @@ export default {
|
|||
dir="auto"
|
||||
:disabled="isSubmitting"
|
||||
name="note[note]"
|
||||
class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
|
||||
class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area qa-comment-input"
|
||||
data-supports-quick-actions="true"
|
||||
:aria-label="__('Description')"
|
||||
:placeholder="__('Write a comment or drag your files here…')"
|
||||
|
|
|
@ -337,7 +337,7 @@ export default {
|
|||
v-model="updatedNoteBody"
|
||||
:data-supports-quick-actions="!isEditing"
|
||||
name="note[note]"
|
||||
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
|
||||
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form qa-reply-input"
|
||||
dir="auto"
|
||||
:aria-label="__('Description')"
|
||||
:placeholder="__('Write a comment or drag your files here…')"
|
||||
|
|
|
@ -5,7 +5,7 @@ import axios from '~/lib/utils/axios_utils';
|
|||
import PerformanceBarService from './services/performance_bar_service';
|
||||
import PerformanceBarStore from './stores/performance_bar_store';
|
||||
|
||||
export default ({ container }) =>
|
||||
const initPerformanceBar = ({ container }) =>
|
||||
new Vue({
|
||||
el: container,
|
||||
components: {
|
||||
|
@ -118,3 +118,9 @@ export default ({ container }) =>
|
|||
});
|
||||
},
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initPerformanceBar({ container: '#js-peek' });
|
||||
});
|
||||
|
||||
export default initPerformanceBar;
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import createEventHub from '~/helpers/event_hub_factory';
|
||||
|
||||
export default createEventHub();
|
|
@ -1,27 +0,0 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import eventHub from './event_hub';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
hasStatus: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
buttonText() {
|
||||
return this.hasStatus ? s__('SetStatusModal|Edit status') : s__('SetStatusModal|Set status');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
eventHub.$emit('openModal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button type="button" class="btn menu-item" @click="openModal">{{ buttonText }}</button>
|
||||
</template>
|
|
@ -6,7 +6,6 @@ import { GlModal, GlTooltipDirective, GlIcon } from '@gitlab/ui';
|
|||
import { deprecatedCreateFlash as createFlash } from '~/flash';
|
||||
import { __, s__ } from '~/locale';
|
||||
import Api from '~/api';
|
||||
import eventHub from './event_hub';
|
||||
import EmojiMenuInModal from './emoji_menu_in_modal';
|
||||
import * as Emoji from '~/emoji';
|
||||
|
||||
|
@ -48,15 +47,12 @@ export default {
|
|||
},
|
||||
},
|
||||
mounted() {
|
||||
eventHub.$on('openModal', this.openModal);
|
||||
this.$root.$emit('bv::show::modal', this.modalId);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.emojiMenu.destroy();
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.$root.$emit('bv::show::modal', this.modalId);
|
||||
},
|
||||
closeModal() {
|
||||
this.$root.$emit('bv::hide::modal', this.modalId);
|
||||
},
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<script>
|
||||
import { GlLoadingIcon, GlTable } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlLoadingIcon,
|
||||
GlTable,
|
||||
},
|
||||
props: {
|
||||
alert: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
tableHeader: {
|
||||
[s__('AlertManagement|Full Alert Payload')]: s__('AlertManagement|Value'),
|
||||
},
|
||||
computed: {
|
||||
items() {
|
||||
if (!this.alert) {
|
||||
return [];
|
||||
}
|
||||
return [{ ...this.$options.tableHeader, ...this.alert }];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-table
|
||||
class="alert-management-details-table"
|
||||
:busy="loading"
|
||||
:empty-text="s__('AlertManagement|No alert data to display.')"
|
||||
:items="items"
|
||||
show-empty
|
||||
stacked
|
||||
>
|
||||
<template #table-busy>
|
||||
<gl-loading-icon size="lg" color="dark" class="gl-mt-5" />
|
||||
</template>
|
||||
</gl-table>
|
||||
</template>
|
|
@ -1,4 +1,6 @@
|
|||
<script>
|
||||
/* eslint-disable vue/no-v-html */
|
||||
|
||||
/**
|
||||
* Common component to render a system note, icon and user information.
|
||||
*
|
||||
|
@ -106,7 +108,7 @@ export default {
|
|||
:class="{ target: isTargetNote, 'pr-0': shouldShowDescriptionVersion }"
|
||||
class="note system-note note-wrapper"
|
||||
>
|
||||
<div v-safe-html="iconHtml" class="timeline-icon"></div>
|
||||
<div class="timeline-icon" v-html="iconHtml"></div>
|
||||
<div class="timeline-content">
|
||||
<div class="note-header">
|
||||
<note-header :author="note.author" :created-at="note.created_at" :note-id="note.id">
|
||||
|
|
|
@ -152,6 +152,18 @@ $red-800: #8d1300 !default;
|
|||
$red-900: #660e00 !default;
|
||||
$red-950: #4d0a00 !default;
|
||||
|
||||
$purple-50: #f4f0ff !default;
|
||||
$purple-100: #e1d8f9 !default;
|
||||
$purple-200: #cbbbf2 !default;
|
||||
$purple-300: #ac93e6 !default;
|
||||
$purple-400: #9475db !default;
|
||||
$purple-500: #7b58cf !default;
|
||||
$purple-600: #694cc0 !default;
|
||||
$purple-700: #5943b6 !default;
|
||||
$purple-800: #453894 !default;
|
||||
$purple-900: #2f2a6b !default;
|
||||
$purple-950: #232150 !default;
|
||||
|
||||
$gray-10: #fafafa !default;
|
||||
$gray-50: #f0f0f0 !default;
|
||||
$gray-100: #dbdbdb !default;
|
||||
|
@ -221,6 +233,20 @@ $reds: (
|
|||
'950': $red-950
|
||||
);
|
||||
|
||||
$purples: (
|
||||
'50': $purple-50,
|
||||
'100': $purple-100,
|
||||
'200': $purple-200,
|
||||
'300': $purple-300,
|
||||
'400': $purple-400,
|
||||
'500': $purple-500,
|
||||
'600': $purple-600,
|
||||
'700': $purple-700,
|
||||
'800': $purple-800,
|
||||
'900': $purple-900,
|
||||
'950': $purple-950
|
||||
);
|
||||
|
||||
$grays: (
|
||||
'10': $gray-10,
|
||||
'50': $gray-50,
|
||||
|
|
|
@ -10,13 +10,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
include SpammableActions
|
||||
include RecordUserLastActivity
|
||||
|
||||
def issue_except_actions
|
||||
%i[index calendar new create bulk_update import_csv export_csv service_desk]
|
||||
end
|
||||
|
||||
def set_issuables_index_only_actions
|
||||
%i[index calendar service_desk]
|
||||
end
|
||||
ISSUES_EXCEPT_ACTIONS = %i[index calendar new create bulk_update import_csv export_csv service_desk].freeze
|
||||
SET_ISSUEABLES_INDEX_ONLY_ACTIONS = %i[index calendar service_desk].freeze
|
||||
|
||||
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
|
||||
prepend_before_action(only: [:calendar]) { authenticate_sessionless_user!(:ics) }
|
||||
|
@ -25,10 +20,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
|
||||
before_action :check_issues_available!
|
||||
before_action :issue, unless: ->(c) { c.issue_except_actions.include?(c.action_name.to_sym) }
|
||||
after_action :log_issue_show, unless: ->(c) { c.issue_except_actions.include?(c.action_name.to_sym) }
|
||||
before_action :issue, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) }
|
||||
after_action :log_issue_show, unless: ->(c) { ISSUES_EXCEPT_ACTIONS.include?(c.action_name.to_sym) }
|
||||
|
||||
before_action :set_issuables_index, if: ->(c) { c.set_issuables_index_only_actions.include?(c.action_name.to_sym) }
|
||||
before_action :set_issuables_index, if: ->(c) { SET_ISSUEABLES_INDEX_ONLY_ACTIONS.include?(c.action_name.to_sym) }
|
||||
|
||||
# Allow write(create) issue
|
||||
before_action :authorize_create_issue!, only: [:new, :create]
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Interface to expose todos for the current_user on the `object`
|
||||
module Types
|
||||
module CurrentUserTodos
|
||||
include BaseInterface
|
||||
|
||||
field_class Types::BaseField
|
||||
|
||||
field :current_user_todos, Types::TodoType.connection_type,
|
||||
description: 'Todos for the current user',
|
||||
null: false do
|
||||
argument :state, Types::TodoStateEnum,
|
||||
description: 'State of the todos',
|
||||
required: false
|
||||
end
|
||||
|
||||
def current_user_todos(state: nil)
|
||||
state ||= %i(done pending) # TodosFinder treats a `nil` state param as `pending`
|
||||
|
||||
TodosFinder.new(current_user, state: state, type: object.class.name, target_id: object.id).execute
|
||||
end
|
||||
end
|
||||
end
|
|
@ -12,6 +12,7 @@ module Types
|
|||
|
||||
implements(Types::Notes::NoteableType)
|
||||
implements(Types::DesignManagement::DesignFields)
|
||||
implements(Types::CurrentUserTodos)
|
||||
|
||||
field :versions,
|
||||
Types::DesignManagement::VersionType.connection_type,
|
||||
|
|
|
@ -7,6 +7,7 @@ module Types
|
|||
connection_type_class(Types::CountableConnectionType)
|
||||
|
||||
implements(Types::Notes::NoteableType)
|
||||
implements(Types::CurrentUserTodos)
|
||||
|
||||
authorize :read_issue
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ module Types
|
|||
connection_type_class(Types::CountableConnectionType)
|
||||
|
||||
implements(Types::Notes::NoteableType)
|
||||
implements(Types::CurrentUserTodos)
|
||||
|
||||
authorize :read_merge_request
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ module Types
|
|||
resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, todo.group_id).find }
|
||||
|
||||
field :author, Types::UserType,
|
||||
description: 'The owner of this todo',
|
||||
description: 'The author of this todo',
|
||||
null: false,
|
||||
resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, todo.author_id).find }
|
||||
|
||||
|
|
|
@ -25,10 +25,6 @@ module HasWiki
|
|||
wiki.repository_exists?
|
||||
end
|
||||
|
||||
def after_wiki_activity
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_wiki_path_conflict
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
require 'carrierwave/orm/activerecord'
|
||||
|
||||
class Project < ApplicationRecord
|
||||
extend ::Gitlab::Utils::Override
|
||||
include Gitlab::ConfigHelper
|
||||
include Gitlab::VisibilityLevel
|
||||
include AccessRequestable
|
||||
|
@ -2470,11 +2469,6 @@ class Project < ApplicationRecord
|
|||
jira_imports.last
|
||||
end
|
||||
|
||||
override :after_wiki_activity
|
||||
def after_wiki_activity
|
||||
touch(:last_activity_at, :last_repository_updated_at)
|
||||
end
|
||||
|
||||
def metrics_setting
|
||||
super || build_metrics_setting
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ module ChatMessage
|
|||
attr_reader :merge_request_iid
|
||||
attr_reader :source_branch
|
||||
attr_reader :target_branch
|
||||
attr_reader :action
|
||||
attr_reader :state
|
||||
attr_reader :title
|
||||
|
||||
|
@ -16,6 +17,7 @@ module ChatMessage
|
|||
@merge_request_iid = obj_attr[:iid]
|
||||
@source_branch = obj_attr[:source_branch]
|
||||
@target_branch = obj_attr[:target_branch]
|
||||
@action = obj_attr[:action]
|
||||
@state = obj_attr[:state]
|
||||
@title = format_title(obj_attr[:title])
|
||||
end
|
||||
|
@ -63,11 +65,17 @@ module ChatMessage
|
|||
"#{project_url}/-/merge_requests/#{merge_request_iid}"
|
||||
end
|
||||
|
||||
# overridden in EE
|
||||
def state_or_action_text
|
||||
case action
|
||||
when 'approved', 'unapproved'
|
||||
action
|
||||
when 'approval'
|
||||
'added their approval to'
|
||||
when 'unapproval'
|
||||
'removed their approval from'
|
||||
else
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ChatMessage::MergeMessage.prepend_if_ee('::EE::ChatMessage::MergeMessage')
|
||||
|
|
|
@ -10,6 +10,23 @@ class ProjectWiki < Wiki
|
|||
def disk_path(*args, &block)
|
||||
container.disk_path + '.wiki'
|
||||
end
|
||||
|
||||
override :after_wiki_activity
|
||||
def after_wiki_activity
|
||||
# Update activity columns, this is done synchronously to avoid
|
||||
# replication delays in Geo.
|
||||
project.touch(:last_activity_at, :last_repository_updated_at)
|
||||
end
|
||||
|
||||
override :after_post_receive
|
||||
def after_post_receive
|
||||
# Update storage statistics
|
||||
ProjectCacheWorker.perform_async(project.id, [], [:wiki_size])
|
||||
|
||||
# This call is repeated for post-receive, to make sure we're updating
|
||||
# the activity columns for Git pushes as well.
|
||||
after_wiki_activity
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove this once we implement ES support for group wikis.
|
||||
|
|
|
@ -133,8 +133,9 @@ class Wiki
|
|||
commit = commit_details(:created, message, title)
|
||||
|
||||
wiki.write_page(title, format.to_sym, content, commit)
|
||||
after_wiki_activity
|
||||
|
||||
update_container_activity
|
||||
true
|
||||
rescue Gitlab::Git::Wiki::DuplicatePageError => e
|
||||
@error_message = "Duplicate page: #{e.message}"
|
||||
false
|
||||
|
@ -144,16 +145,18 @@ class Wiki
|
|||
commit = commit_details(:updated, message, page.title)
|
||||
|
||||
wiki.update_page(page.path, title || page.name, format.to_sym, content, commit)
|
||||
after_wiki_activity
|
||||
|
||||
update_container_activity
|
||||
true
|
||||
end
|
||||
|
||||
def delete_page(page, message = nil)
|
||||
return unless page
|
||||
|
||||
wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
|
||||
after_wiki_activity
|
||||
|
||||
update_container_activity
|
||||
true
|
||||
end
|
||||
|
||||
def page_title_and_dir(title)
|
||||
|
@ -209,6 +212,17 @@ class Wiki
|
|||
web_url(only_path: true).sub(%r{/#{Wiki::HOMEPAGE}\z}, '')
|
||||
end
|
||||
|
||||
# Callbacks for synchronous processing after wiki changes.
|
||||
# These will be executed after any change made through GitLab itself (web UI and API),
|
||||
# but not for Git pushes.
|
||||
def after_wiki_activity
|
||||
end
|
||||
|
||||
# Callbacks for background processing after wiki changes.
|
||||
# These will be executed after any change to the wiki repository.
|
||||
def after_post_receive
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def commit_details(action, message = nil, title = nil)
|
||||
|
@ -225,10 +239,6 @@ class Wiki
|
|||
def default_message(action, title)
|
||||
"#{user.username} #{action} page: #{title}"
|
||||
end
|
||||
|
||||
def update_container_activity
|
||||
container.after_wiki_activity
|
||||
end
|
||||
end
|
||||
|
||||
Wiki.prepend_if_ee('EE::Wiki')
|
||||
|
|
|
@ -5,7 +5,16 @@ module Git
|
|||
# Maximum number of change events we will process on any single push
|
||||
MAX_CHANGES = 100
|
||||
|
||||
attr_reader :wiki
|
||||
|
||||
def initialize(wiki, current_user, params)
|
||||
@wiki, @current_user, @params = wiki, current_user, params.dup
|
||||
end
|
||||
|
||||
def execute
|
||||
# Execute model-specific callbacks
|
||||
wiki.after_post_receive
|
||||
|
||||
process_changes
|
||||
end
|
||||
|
||||
|
@ -23,7 +32,11 @@ module Git
|
|||
end
|
||||
|
||||
def can_process_wiki_events?
|
||||
Feature.enabled?(:wiki_events_on_git_push, project)
|
||||
# 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)
|
||||
end
|
||||
|
||||
def push_changes
|
||||
|
@ -36,10 +49,6 @@ module Git
|
|||
wiki.repository.raw.raw_changes_between(change[:oldrev], change[:newrev])
|
||||
end
|
||||
|
||||
def wiki
|
||||
project.wiki
|
||||
end
|
||||
|
||||
def create_event_for(change)
|
||||
event_service.execute(
|
||||
change.last_known_slug,
|
||||
|
@ -54,7 +63,7 @@ module Git
|
|||
end
|
||||
|
||||
def on_default_branch?(change)
|
||||
project.wiki.default_branch == ::Gitlab::Git.branch_name(change[:ref])
|
||||
wiki.default_branch == ::Gitlab::Git.branch_name(change[:ref])
|
||||
end
|
||||
|
||||
# See: [Gitlab::GitPostReceive#changes]
|
||||
|
|
|
@ -5,11 +5,11 @@ module Git
|
|||
class Change
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
# @param [ProjectWiki] wiki
|
||||
# @param [Wiki] wiki
|
||||
# @param [Hash] change - must have keys `:oldrev` and `:newrev`
|
||||
# @param [Gitlab::Git::RawDiffChange] raw_change
|
||||
def initialize(project_wiki, change, raw_change)
|
||||
@wiki, @raw_change, @change = project_wiki, raw_change, change
|
||||
def initialize(wiki, change, raw_change)
|
||||
@wiki, @raw_change, @change = wiki, raw_change, change
|
||||
end
|
||||
|
||||
def page
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
= yield :page_specific_javascripts
|
||||
|
||||
= webpack_controller_bundle_tags
|
||||
= webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
|
||||
= webpack_bundle_tag "chrome_84_icon_fix" if browser.chrome?([">=84", "<84.0.4147.125"]) || browser.edge?([">=84", "<84.0.522.59"])
|
||||
|
||||
= yield :project_javascripts
|
||||
|
|
|
@ -14,7 +14,11 @@
|
|||
%li.divider
|
||||
- if can?(current_user, :update_user_status, current_user)
|
||||
%li
|
||||
.js-set-status-modal-trigger{ data: { has_status: current_user.status.present? ? 'true' : 'false' } }
|
||||
%button.btn.menu-item.js-set-status-modal-trigger{ type: 'button' }
|
||||
- if current_user.status.present?
|
||||
= s_('SetStatusModal|Edit status')
|
||||
- else
|
||||
= s_('SetStatusModal|Set status')
|
||||
- if current_user_menu?(:profile)
|
||||
%li
|
||||
= link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username }
|
||||
|
|
|
@ -12,8 +12,8 @@ class PostReceive # rubocop:disable Scalability/IdempotentWorker
|
|||
def perform(gl_repository, identifier, changes, push_options = {})
|
||||
container, project, repo_type = Gitlab::GlRepository.parse(gl_repository)
|
||||
|
||||
if project.nil? && (!repo_type.snippet? || container.is_a?(ProjectSnippet))
|
||||
log("Triggered hook for non-existing project with gl_repository \"#{gl_repository}\"")
|
||||
if container.nil? || (container.is_a?(ProjectSnippet) && project.nil?)
|
||||
log("Triggered hook for non-existing gl_repository \"#{gl_repository}\"")
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -24,7 +24,7 @@ class PostReceive # rubocop:disable Scalability/IdempotentWorker
|
|||
post_received = Gitlab::GitPostReceive.new(container, identifier, changes, push_options)
|
||||
|
||||
if repo_type.wiki?
|
||||
process_wiki_changes(post_received, container)
|
||||
process_wiki_changes(post_received, container.wiki)
|
||||
elsif repo_type.project?
|
||||
process_project_changes(post_received, container)
|
||||
elsif repo_type.snippet?
|
||||
|
@ -59,18 +59,15 @@ class PostReceive # rubocop:disable Scalability/IdempotentWorker
|
|||
after_project_changes_hooks(project, user, changes.refs, changes.repository_data)
|
||||
end
|
||||
|
||||
def process_wiki_changes(post_received, project)
|
||||
project.touch(:last_activity_at, :last_repository_updated_at)
|
||||
project.wiki.repository.expire_statistics_caches
|
||||
ProjectCacheWorker.perform_async(project.id, [], [:wiki_size])
|
||||
|
||||
def process_wiki_changes(post_received, wiki)
|
||||
user = identify_user(post_received)
|
||||
return false unless user
|
||||
|
||||
# We only need to expire certain caches once per push
|
||||
expire_caches(post_received, project.wiki.repository)
|
||||
expire_caches(post_received, wiki.repository)
|
||||
wiki.repository.expire_statistics_caches
|
||||
|
||||
::Git::WikiPushService.new(project, user, changes: post_received.changes).execute
|
||||
::Git::WikiPushService.new(wiki, user, changes: post_received.changes).execute
|
||||
end
|
||||
|
||||
def process_snippet_changes(post_received, snippet)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Expose the todos of the current user on relevant objects in GraphQL
|
||||
merge_request: 40555
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace v-html with v-safe-html in popover.vue
|
||||
merge_request: 41197
|
||||
author: Kev @KevSlashNull
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Rake task to generate raw SQLs for usage ping
|
||||
merge_request: 41091
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Use AppLogger in listener.rb, cleaner.rake, helpers.rb and spec files
|
||||
merge_request: 41116
|
||||
author: Rajendra Kadam
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix merge request chat messages for adding and removing approvals
|
||||
merge_request: 41775
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update design discussions to use GitLab UI components
|
||||
merge_request: 41686
|
||||
author:
|
||||
type: other
|
|
@ -1,5 +1,7 @@
|
|||
class DirectUploadsValidator
|
||||
SUPPORTED_DIRECT_UPLOAD_PROVIDERS = %w(Google AWS AzureRM).freeze
|
||||
SUPPORTED_DIRECT_UPLOAD_PROVIDERS = [ObjectStorage::Config::GOOGLE_PROVIDER,
|
||||
ObjectStorage::Config::AWS_PROVIDER,
|
||||
ObjectStorage::Config::AZURE_PROVIDER].freeze
|
||||
|
||||
ValidationError = Class.new(StandardError)
|
||||
|
||||
|
@ -24,7 +26,7 @@ class DirectUploadsValidator
|
|||
def provider_loaded?(provider)
|
||||
return false unless SUPPORTED_DIRECT_UPLOAD_PROVIDERS.include?(provider)
|
||||
|
||||
require 'fog/azurerm' if provider == 'AzureRM'
|
||||
require 'fog/azurerm' if provider == ObjectStorage::Config::AZURE_PROVIDER
|
||||
|
||||
true
|
||||
end
|
||||
|
|
|
@ -79,6 +79,7 @@ function generateEntries() {
|
|||
const manualEntries = {
|
||||
default: defaultEntries,
|
||||
sentry: './sentry/index.js',
|
||||
performance_bar: './performance_bar/index.js',
|
||||
chrome_84_icon_fix: './lib/chrome_84_icon_fix.js',
|
||||
};
|
||||
|
||||
|
|
|
@ -2944,6 +2944,38 @@ type CreateSnippetPayload {
|
|||
snippet: Snippet
|
||||
}
|
||||
|
||||
interface CurrentUserTodos {
|
||||
"""
|
||||
Todos for the current user
|
||||
"""
|
||||
currentUserTodos(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
State of the todos
|
||||
"""
|
||||
state: TodoStateEnum
|
||||
): TodoConnection!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DastOnDemandScanCreate
|
||||
"""
|
||||
|
@ -3501,7 +3533,37 @@ type DeleteJobsResponse {
|
|||
"""
|
||||
A single design
|
||||
"""
|
||||
type Design implements DesignFields & Noteable {
|
||||
type Design implements CurrentUserTodos & DesignFields & Noteable {
|
||||
"""
|
||||
Todos for the current user
|
||||
"""
|
||||
currentUserTodos(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
State of the todos
|
||||
"""
|
||||
state: TodoStateEnum
|
||||
): TodoConnection!
|
||||
|
||||
"""
|
||||
The diff refs for this design
|
||||
"""
|
||||
|
@ -4896,7 +4958,7 @@ type EnvironmentEdge {
|
|||
"""
|
||||
Represents an epic.
|
||||
"""
|
||||
type Epic implements Noteable {
|
||||
type Epic implements CurrentUserTodos & Noteable {
|
||||
"""
|
||||
Author of the epic
|
||||
"""
|
||||
|
@ -4999,6 +5061,36 @@ type Epic implements Noteable {
|
|||
"""
|
||||
createdAt: Time
|
||||
|
||||
"""
|
||||
Todos for the current user
|
||||
"""
|
||||
currentUserTodos(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
State of the todos
|
||||
"""
|
||||
state: TodoStateEnum
|
||||
): TodoConnection!
|
||||
|
||||
"""
|
||||
Number of open and closed descendant epics and issues
|
||||
"""
|
||||
|
@ -5438,7 +5530,7 @@ type EpicHealthStatus {
|
|||
"""
|
||||
Relationship between an epic and an issue
|
||||
"""
|
||||
type EpicIssue implements Noteable {
|
||||
type EpicIssue implements CurrentUserTodos & Noteable {
|
||||
"""
|
||||
Alert associated to this issue
|
||||
"""
|
||||
|
@ -5494,6 +5586,36 @@ type EpicIssue implements Noteable {
|
|||
"""
|
||||
createdAt: Time!
|
||||
|
||||
"""
|
||||
Todos for the current user
|
||||
"""
|
||||
currentUserTodos(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
State of the todos
|
||||
"""
|
||||
state: TodoStateEnum
|
||||
): TodoConnection!
|
||||
|
||||
"""
|
||||
Description of the issue
|
||||
"""
|
||||
|
@ -7328,7 +7450,7 @@ enum IssuableState {
|
|||
opened
|
||||
}
|
||||
|
||||
type Issue implements Noteable {
|
||||
type Issue implements CurrentUserTodos & Noteable {
|
||||
"""
|
||||
Alert associated to this issue
|
||||
"""
|
||||
|
@ -7384,6 +7506,36 @@ type Issue implements Noteable {
|
|||
"""
|
||||
createdAt: Time!
|
||||
|
||||
"""
|
||||
Todos for the current user
|
||||
"""
|
||||
currentUserTodos(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
State of the todos
|
||||
"""
|
||||
state: TodoStateEnum
|
||||
): TodoConnection!
|
||||
|
||||
"""
|
||||
Description of the issue
|
||||
"""
|
||||
|
@ -8964,7 +9116,7 @@ type MemberInterfaceEdge {
|
|||
node: MemberInterface
|
||||
}
|
||||
|
||||
type MergeRequest implements Noteable {
|
||||
type MergeRequest implements CurrentUserTodos & Noteable {
|
||||
"""
|
||||
Indicates if members of the target project can push to the fork
|
||||
"""
|
||||
|
@ -9040,6 +9192,36 @@ type MergeRequest implements Noteable {
|
|||
"""
|
||||
createdAt: Time!
|
||||
|
||||
"""
|
||||
Todos for the current user
|
||||
"""
|
||||
currentUserTodos(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
|
||||
"""
|
||||
State of the todos
|
||||
"""
|
||||
state: TodoStateEnum
|
||||
): TodoConnection!
|
||||
|
||||
"""
|
||||
Default merge commit message of the merge request
|
||||
"""
|
||||
|
@ -16234,7 +16416,7 @@ type Todo {
|
|||
action: TodoActionEnum!
|
||||
|
||||
"""
|
||||
The owner of this todo
|
||||
The author of this todo
|
||||
"""
|
||||
author: User!
|
||||
|
||||
|
|
|
@ -7985,6 +7985,110 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "CurrentUserTodos",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "currentUserTodos",
|
||||
"description": "Todos for the current user",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the todos",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "TodoStateEnum",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TodoConnection",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": [
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Design",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Epic",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "EpicIssue",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Issue",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "MergeRequest",
|
||||
"ofType": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DastOnDemandScanCreateInput",
|
||||
|
@ -9560,6 +9664,73 @@
|
|||
"name": "Design",
|
||||
"description": "A single design",
|
||||
"fields": [
|
||||
{
|
||||
"name": "currentUserTodos",
|
||||
"description": "Todos for the current user",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the todos",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "TodoStateEnum",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TodoConnection",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "diffRefs",
|
||||
"description": "The diff refs for this design",
|
||||
|
@ -9939,6 +10110,11 @@
|
|||
"kind": "INTERFACE",
|
||||
"name": "DesignFields",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "CurrentUserTodos",
|
||||
"ofType": null
|
||||
}
|
||||
],
|
||||
"enumValues": null,
|
||||
|
@ -13998,6 +14174,73 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "currentUserTodos",
|
||||
"description": "Todos for the current user",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the todos",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "TodoStateEnum",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TodoConnection",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "descendantCounts",
|
||||
"description": "Number of open and closed descendant epics and issues",
|
||||
|
@ -14777,6 +15020,11 @@
|
|||
"kind": "INTERFACE",
|
||||
"name": "Noteable",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "CurrentUserTodos",
|
||||
"ofType": null
|
||||
}
|
||||
],
|
||||
"enumValues": null,
|
||||
|
@ -15375,6 +15623,73 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "currentUserTodos",
|
||||
"description": "Todos for the current user",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the todos",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "TodoStateEnum",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TodoConnection",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"description": "Description of the issue",
|
||||
|
@ -16155,6 +16470,11 @@
|
|||
"kind": "INTERFACE",
|
||||
"name": "Noteable",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "CurrentUserTodos",
|
||||
"ofType": null
|
||||
}
|
||||
],
|
||||
"enumValues": null,
|
||||
|
@ -20371,6 +20691,73 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "currentUserTodos",
|
||||
"description": "Todos for the current user",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the todos",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "TodoStateEnum",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TodoConnection",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"description": "Description of the issue",
|
||||
|
@ -21123,6 +21510,11 @@
|
|||
"kind": "INTERFACE",
|
||||
"name": "Noteable",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "CurrentUserTodos",
|
||||
"ofType": null
|
||||
}
|
||||
],
|
||||
"enumValues": null,
|
||||
|
@ -25130,6 +25522,73 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "currentUserTodos",
|
||||
"description": "Todos for the current user",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "state",
|
||||
"description": "State of the todos",
|
||||
"type": {
|
||||
"kind": "ENUM",
|
||||
"name": "TodoStateEnum",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TodoConnection",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "defaultMergeCommitMessage",
|
||||
"description": "Default merge commit message of the merge request",
|
||||
|
@ -26288,6 +26747,11 @@
|
|||
"kind": "INTERFACE",
|
||||
"name": "Noteable",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "CurrentUserTodos",
|
||||
"ofType": null
|
||||
}
|
||||
],
|
||||
"enumValues": null,
|
||||
|
@ -47766,7 +48230,7 @@
|
|||
},
|
||||
{
|
||||
"name": "author",
|
||||
"description": "The owner of this todo",
|
||||
"description": "The author of this todo",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
|
|
@ -2374,7 +2374,7 @@ Representing a todo entry
|
|||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `action` | TodoActionEnum! | Action of the todo |
|
||||
| `author` | User! | The owner of this todo |
|
||||
| `author` | User! | The author of this todo |
|
||||
| `body` | String! | Body of the todo |
|
||||
| `createdAt` | Time! | Timestamp this todo was created |
|
||||
| `group` | Group | Group this todo is associated with |
|
||||
|
|
|
@ -74,8 +74,8 @@ team members can join the Zoom call without requesting a link.
|
|||
|
||||
For information about GitLab and incident management, see:
|
||||
|
||||
- [Generic alerts](./generic_alerts.md)
|
||||
- [Alerts](./alerts.md)
|
||||
- [Alert details](./alert_details.md)
|
||||
- [Incidents](./incidents.md)
|
||||
- [Status page](./status_page.md)
|
||||
- [Generic alerts](generic_alerts.md)
|
||||
- [Alerts](alerts.md)
|
||||
- [Alert details](alert_details.md)
|
||||
- [Incidents](incidents.md)
|
||||
- [Status page](status_page.md)
|
||||
|
|
|
@ -606,7 +606,7 @@ Example profile definition:
|
|||
|
||||
```yaml
|
||||
Profiles:
|
||||
- Name: Quick-10
|
||||
- Name: Quick-10
|
||||
DefaultProfile: Quick
|
||||
Routes:
|
||||
- Route: *Route0
|
||||
|
@ -632,7 +632,7 @@ Profiles:
|
|||
To turn off the General Fuzzing Check you can remove these lines:
|
||||
|
||||
```yaml
|
||||
- Name: GeneralFuzzingCheck
|
||||
- Name: GeneralFuzzingCheck
|
||||
Configuration:
|
||||
FuzzingCount: 10
|
||||
UnicodeFuzzing: true
|
||||
|
@ -671,7 +671,7 @@ This example shows the FormBody Fuzzing Check:
|
|||
|
||||
```yaml
|
||||
Checks:
|
||||
- Name: FormBodyFuzzingCheck
|
||||
- Name: FormBodyFuzzingCheck
|
||||
Configuration:
|
||||
FuzzingCount: 30
|
||||
UnicodeFuzzing: true
|
||||
|
@ -688,7 +688,7 @@ example provides only the other two Assertions (`LogAnalysisAssertion`,
|
|||
|
||||
```yaml
|
||||
Profiles:
|
||||
- Name: Quick-10
|
||||
- Name: Quick-10
|
||||
DefaultProfile: Quick
|
||||
Routes:
|
||||
- Route: *Route0
|
||||
|
|
|
@ -166,7 +166,8 @@ reports. You can specify the list of all headers to be masked. For details, see
|
|||
|
||||
### Dismissing a vulnerability
|
||||
|
||||
To dismiss a vulnerability, you must set its status to Dismissed. Follow these steps to do so:
|
||||
To dismiss a vulnerability, you must set its status to Dismissed. This dismisses the vulnerability
|
||||
for the entire project. Follow these steps to do so:
|
||||
|
||||
1. Select the vulnerability in the Security Dashboard.
|
||||
1. Select **Dismissed** from the **Status** selector menu at the top-right.
|
||||
|
|
|
@ -523,13 +523,13 @@ For details on saving and transporting Docker images as a file, see Docker's doc
|
|||
Add the following configuration to your `.gitlab-ci.yml` file. You must replace
|
||||
`SECURE_ANALYZERS_PREFIX` to refer to your local Docker container registry:
|
||||
|
||||
```yaml
|
||||
```yaml
|
||||
include:
|
||||
- template: SAST.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
SECURE_ANALYZERS_PREFIX: "localhost:5000/analyzers"
|
||||
```
|
||||
```
|
||||
|
||||
The SAST job should now use local copies of the SAST analyzers to scan your code and generate
|
||||
security reports without requiring internet access.
|
||||
|
|
|
@ -268,8 +268,7 @@ You can supply a custom root certificate to complete TLS verification by using t
|
|||
#### Using private Python repos
|
||||
|
||||
If you have a private Python repository you can use the `PIP_INDEX_URL` [environment variable](#available-variables)
|
||||
to specify its location. It's also possible to provide a custom `pip.conf` for
|
||||
[additional configuration](#custom-root-certificates-for-python).
|
||||
to specify its location.
|
||||
|
||||
### Configuring NPM projects
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ module Backup
|
|||
return
|
||||
end
|
||||
|
||||
directory = connect_to_remote_directory(connection_settings)
|
||||
directory = connect_to_remote_directory(Gitlab.config.backup.upload)
|
||||
|
||||
if directory.files.create(create_attributes)
|
||||
progress.puts "done".color(:green)
|
||||
|
@ -195,9 +195,11 @@ module Backup
|
|||
@backup_file_list.map {|item| item.gsub("#{FILE_NAME_SUFFIX}", "")}
|
||||
end
|
||||
|
||||
def connect_to_remote_directory(connection_settings)
|
||||
# our settings use string keys, but Fog expects symbols
|
||||
connection = ::Fog::Storage.new(connection_settings.symbolize_keys)
|
||||
def connect_to_remote_directory(options)
|
||||
config = ObjectStorage::Config.new(options)
|
||||
config.load_provider
|
||||
|
||||
connection = ::Fog::Storage.new(config.credentials)
|
||||
|
||||
# We only attempt to create the directory for local backups. For AWS
|
||||
# and other cloud providers, we cannot guarantee the user will have
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class UsageDataQueries < UsageData
|
||||
class << self
|
||||
def count(relation, column = nil, *rest)
|
||||
raw_sql(relation, column)
|
||||
end
|
||||
|
||||
def distinct_count(relation, column = nil, *rest)
|
||||
raw_sql(relation, column, :distinct)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def raw_sql(relation, column, distinct = nil)
|
||||
column ||= relation.primary_key
|
||||
relation.select(relation.all.table[column].count(distinct)).to_sql
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,12 +2,26 @@
|
|||
|
||||
module ObjectStorage
|
||||
class Config
|
||||
AWS_PROVIDER = 'AWS'
|
||||
AZURE_PROVIDER = 'AzureRM'
|
||||
GOOGLE_PROVIDER = 'Google'
|
||||
|
||||
attr_reader :options
|
||||
|
||||
def initialize(options)
|
||||
@options = options.to_hash.deep_symbolize_keys
|
||||
end
|
||||
|
||||
def load_provider
|
||||
if aws?
|
||||
require 'fog/aws'
|
||||
elsif google?
|
||||
require 'fog/google'
|
||||
elsif azure?
|
||||
require 'fog/azurerm'
|
||||
end
|
||||
end
|
||||
|
||||
def credentials
|
||||
@credentials ||= options[:connection] || {}
|
||||
end
|
||||
|
@ -30,7 +44,7 @@ module ObjectStorage
|
|||
|
||||
# AWS-specific options
|
||||
def aws?
|
||||
provider == 'AWS'
|
||||
provider == AWS_PROVIDER
|
||||
end
|
||||
|
||||
def use_iam_profile?
|
||||
|
@ -61,11 +75,11 @@ module ObjectStorage
|
|||
# End Azure-specific options
|
||||
|
||||
def google?
|
||||
provider == 'Google'
|
||||
provider == GOOGLE_PROVIDER
|
||||
end
|
||||
|
||||
def azure?
|
||||
provider == 'AzureRM'
|
||||
provider == AZURE_PROVIDER
|
||||
end
|
||||
|
||||
def fog_attributes
|
||||
|
|
|
@ -32,21 +32,19 @@ module RspecFlaky
|
|||
flaky_examples[current_example.uid] = flaky_example
|
||||
end
|
||||
|
||||
# rubocop:disable Gitlab/RailsLogger
|
||||
def dump_summary(_)
|
||||
RspecFlaky::Report.new(flaky_examples).write(RspecFlaky::Config.flaky_examples_report_path)
|
||||
# write_report_file(flaky_examples, RspecFlaky::Config.flaky_examples_report_path)
|
||||
|
||||
new_flaky_examples = flaky_examples - suite_flaky_examples
|
||||
if new_flaky_examples.any?
|
||||
Rails.logger.warn "\nNew flaky examples detected:\n"
|
||||
Rails.logger.warn Gitlab::Json.pretty_generate(new_flaky_examples.to_h)
|
||||
Gitlab::AppLogger.warn "\nNew flaky examples detected:\n"
|
||||
Gitlab::AppLogger.warn Gitlab::Json.pretty_generate(new_flaky_examples.to_h)
|
||||
|
||||
RspecFlaky::Report.new(new_flaky_examples).write(RspecFlaky::Config.new_flaky_examples_report_path)
|
||||
# write_report_file(new_flaky_examples, RspecFlaky::Config.new_flaky_examples_report_path)
|
||||
end
|
||||
end
|
||||
# rubocop:enable Gitlab/RailsLogger
|
||||
|
||||
private
|
||||
|
||||
|
|
|
@ -178,19 +178,17 @@ namespace :gitlab do
|
|||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Gitlab/RailsLogger
|
||||
def logger
|
||||
return @logger if defined?(@logger)
|
||||
|
||||
@logger = if Rails.env.development? || Rails.env.production?
|
||||
Logger.new(STDOUT).tap do |stdout_logger|
|
||||
stdout_logger.extend(ActiveSupport::Logger.broadcast(Rails.logger))
|
||||
stdout_logger.extend(ActiveSupport::Logger.broadcast(Gitlab::AppLogger))
|
||||
stdout_logger.level = debug? ? Logger::DEBUG : Logger::INFO
|
||||
end
|
||||
else
|
||||
Rails.logger
|
||||
Gitlab::AppLogger
|
||||
end
|
||||
end
|
||||
# rubocop:enable Gitlab/RailsLogger
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
namespace :gitlab do
|
||||
namespace :usage_data do
|
||||
desc 'GitLab | UsageData | Generate raw SQLs for usage ping in YAML'
|
||||
task dump_sql_in_yaml: :environment do
|
||||
puts Gitlab::UsageDataQueries.uncached_data.to_yaml
|
||||
end
|
||||
|
||||
desc 'GitLab | UsageData | Generate raw SQLs for usage ping in JSON'
|
||||
task dump_sql_in_json: :environment do
|
||||
puts Gitlab::Json.pretty_generate(Gitlab::UsageDataQueries.uncached_data)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2165,6 +2165,9 @@ msgstr ""
|
|||
msgid "AlertManagement|Events"
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertManagement|Full Alert Payload"
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertManagement|High"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2267,6 +2270,9 @@ msgstr ""
|
|||
msgid "AlertManagement|Unknown"
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertManagement|Value"
|
||||
msgstr ""
|
||||
|
||||
msgid "AlertManagement|View alerts in Opsgenie"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2621,6 +2627,9 @@ msgstr ""
|
|||
msgid "An error occurred while committing your changes."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while creating the issue. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while creating the list. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"@babel/preset-env": "^7.10.1",
|
||||
"@gitlab/at.js": "1.5.5",
|
||||
"@gitlab/svgs": "1.164.0",
|
||||
"@gitlab/ui": "20.19.0",
|
||||
"@gitlab/ui": "20.20.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "^6.0.3-1",
|
||||
"@sentry/browser": "^5.22.3",
|
||||
|
|
|
@ -275,7 +275,8 @@ module QA
|
|||
end
|
||||
|
||||
def click_open_in_web_ide
|
||||
click_element :open_in_web_ide_button
|
||||
click_element(:open_in_web_ide_button)
|
||||
wait_for_requests
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,11 @@ module QA
|
|||
|
||||
view 'app/assets/javascripts/ide/components/activity_bar.vue' do
|
||||
element :commit_mode_tab
|
||||
element :edit_mode_tab
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/ide/components/ide_status_bar.vue' do
|
||||
element :commit_sha_content
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/ide/components/ide_tree.vue' do
|
||||
|
@ -104,11 +109,19 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def commit_sha
|
||||
return unless has_element?(:commit_sha_content, wait: 0)
|
||||
|
||||
find_element(:commit_sha_content).text
|
||||
end
|
||||
|
||||
def commit_changes(open_merge_request: false)
|
||||
# Clicking :begin_commit_button switches from the
|
||||
# edit to the commit view
|
||||
click_element :begin_commit_button
|
||||
active_element? :commit_mode_tab
|
||||
click_element(:begin_commit_button)
|
||||
active_element?(:commit_mode_tab)
|
||||
|
||||
original_commit = commit_sha
|
||||
|
||||
# After clicking :begin_commit_button, there is an animation
|
||||
# that hides :begin_commit_button and shows :commit_button
|
||||
|
@ -126,16 +139,17 @@ module QA
|
|||
# Click :commit_button and keep retrying just in case part of the
|
||||
# animation is still in process even when the buttons have the
|
||||
# expected visibility.
|
||||
commit_success_msg_shown = retry_until(sleep_interval: 5) do
|
||||
commit_success = retry_until(sleep_interval: 5) do
|
||||
click_element(:commit_to_current_branch_radio) if has_element?(:commit_to_current_branch_radio)
|
||||
click_element(:commit_button) if has_element?(:commit_button)
|
||||
|
||||
wait_until(reload: false) do
|
||||
has_text?('Your changes have been committed')
|
||||
# If this is the first commit, the commit SHA only appears after reloading
|
||||
wait_until(reload: true) do
|
||||
active_element?(:edit_mode_tab) && commit_sha != original_commit
|
||||
end
|
||||
end
|
||||
|
||||
raise "The changes do not appear to have been committed successfully." unless commit_success_msg_shown
|
||||
raise "The changes do not appear to have been committed successfully." unless commit_success
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import { GlAlert, GlLoadingIcon, GlTable } from '@gitlab/ui';
|
||||
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
|
||||
import axios from 'axios';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
import AlertDetails from '~/alert_management/components/alert_details.vue';
|
||||
import createIssueMutation from '~/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
|
@ -22,8 +23,6 @@ describe('AlertDetails', () => {
|
|||
const projectId = '1';
|
||||
const $router = { replace: jest.fn() };
|
||||
|
||||
const findDetailsTable = () => wrapper.find(GlTable);
|
||||
|
||||
function mountComponent({ data, loading = false, mountMethod = shallowMount, stubs = {} } = {}) {
|
||||
wrapper = mountMethod(AlertDetails, {
|
||||
provide: {
|
||||
|
@ -66,6 +65,7 @@ describe('AlertDetails', () => {
|
|||
const findCreateIncidentBtn = () => wrapper.find('[data-testid="createIncidentBtn"]');
|
||||
const findViewIncidentBtn = () => wrapper.find('[data-testid="viewIncidentBtn"]');
|
||||
const findIncidentCreationAlert = () => wrapper.find('[data-testid="incidentCreationError"]');
|
||||
const findDetailsTable = () => wrapper.find(AlertDetailsTable);
|
||||
|
||||
describe('Alert details', () => {
|
||||
describe('when alert is null', () => {
|
||||
|
|
|
@ -312,7 +312,7 @@ describe('boardsStore', () => {
|
|||
});
|
||||
|
||||
describe('newIssue', () => {
|
||||
const id = 'not-creative';
|
||||
const id = 1;
|
||||
const issue = { some: 'issue data' };
|
||||
const url = `${endpoints.listsEndpoint}/${id}/issues`;
|
||||
const expectedRequest = expect.objectContaining({
|
||||
|
|
|
@ -184,6 +184,7 @@ describe('List model', () => {
|
|||
}),
|
||||
);
|
||||
list.issues = [];
|
||||
global.gon.features = { boardsWithSwimlanes: false };
|
||||
});
|
||||
|
||||
it('adds new issue to top of list', done => {
|
||||
|
|
|
@ -117,6 +117,29 @@ export const mockIssue = {
|
|||
],
|
||||
};
|
||||
|
||||
export const mockIssue2 = {
|
||||
title: 'Planning',
|
||||
id: 2,
|
||||
iid: 2,
|
||||
confidential: false,
|
||||
labels: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'plan',
|
||||
color: 'blue',
|
||||
description: 'planning',
|
||||
},
|
||||
],
|
||||
assignees: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'name',
|
||||
username: 'username',
|
||||
avatar_url: 'http://avatar_url',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const BoardsMockData = {
|
||||
GET: {
|
||||
'/test/-/boards/1/lists/300/issues?id=300&page=1': {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import testAction from 'helpers/vuex_action_helper';
|
||||
import { mockListsWithModel } from '../mock_data';
|
||||
import { mockListsWithModel, mockLists, mockIssue } from '../mock_data';
|
||||
import actions, { gqlClient } from '~/boards/stores/actions';
|
||||
import * as types from '~/boards/stores/mutation_types';
|
||||
import { inactiveId, ListType } from '~/boards/constants';
|
||||
|
@ -236,6 +236,43 @@ describe('createNewIssue', () => {
|
|||
expectNotImplemented(actions.createNewIssue);
|
||||
});
|
||||
|
||||
describe('addListIssue', () => {
|
||||
it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', done => {
|
||||
const payload = {
|
||||
list: mockLists[0],
|
||||
issue: mockIssue,
|
||||
position: 0,
|
||||
};
|
||||
|
||||
testAction(
|
||||
actions.addListIssue,
|
||||
payload,
|
||||
{},
|
||||
[{ type: types.ADD_ISSUE_TO_LIST, payload }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addListIssueFailure', () => {
|
||||
it('should commit UPDATE_LIST_FAILURE mutation when API returns an error', done => {
|
||||
const payload = {
|
||||
list: mockLists[0],
|
||||
issue: mockIssue,
|
||||
};
|
||||
|
||||
testAction(
|
||||
actions.addListIssueFailure,
|
||||
payload,
|
||||
{},
|
||||
[{ type: types.ADD_ISSUE_TO_LIST_FAILURE, payload }],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchBacklog', () => {
|
||||
expectNotImplemented(actions.fetchBacklog);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import mutations from '~/boards/stores/mutations';
|
||||
import * as types from '~/boards/stores/mutation_types';
|
||||
import defaultState from '~/boards/stores/state';
|
||||
import { listObj, listObjDuplicate, mockIssue, mockListsWithModel } from '../mock_data';
|
||||
import {
|
||||
listObj,
|
||||
listObjDuplicate,
|
||||
mockIssue,
|
||||
mockIssue2,
|
||||
mockListsWithModel,
|
||||
mockLists,
|
||||
} from '../mock_data';
|
||||
|
||||
const expectNotImplemented = action => {
|
||||
it('is not implemented', () => {
|
||||
|
@ -148,7 +155,7 @@ describe('Board Store Mutations', () => {
|
|||
describe('RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS', () => {
|
||||
it('sets isLoadingIssues to false and updates issuesByListId object', () => {
|
||||
const listIssues = {
|
||||
'1': [mockIssue.id],
|
||||
'': [mockIssue.id],
|
||||
};
|
||||
const issues = {
|
||||
'1': mockIssue,
|
||||
|
@ -264,6 +271,50 @@ describe('Board Store Mutations', () => {
|
|||
expectNotImplemented(mutations.RECEIVE_UPDATE_ISSUE_ERROR);
|
||||
});
|
||||
|
||||
describe('ADD_ISSUE_TO_LIST', () => {
|
||||
it('adds issue to issues state and issue id in list in issuesByListId', () => {
|
||||
const listIssues = {
|
||||
'gid://gitlab/List/1': [mockIssue.id],
|
||||
};
|
||||
const issues = {
|
||||
'1': mockIssue,
|
||||
};
|
||||
|
||||
state = {
|
||||
...state,
|
||||
issuesByListId: listIssues,
|
||||
issues,
|
||||
};
|
||||
|
||||
mutations.ADD_ISSUE_TO_LIST(state, { list: mockLists[0], issue: mockIssue2 });
|
||||
|
||||
expect(state.issuesByListId['gid://gitlab/List/1']).toContain(mockIssue2.id);
|
||||
expect(state.issues[mockIssue2.id]).toEqual(mockIssue2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ADD_ISSUE_TO_LIST_FAILURE', () => {
|
||||
it('removes issue id from list in issuesByListId', () => {
|
||||
const listIssues = {
|
||||
'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
|
||||
};
|
||||
const issues = {
|
||||
'1': mockIssue,
|
||||
'2': mockIssue2,
|
||||
};
|
||||
|
||||
state = {
|
||||
...state,
|
||||
issuesByListId: listIssues,
|
||||
issues,
|
||||
};
|
||||
|
||||
mutations.ADD_ISSUE_TO_LIST_FAILURE(state, { list: mockLists[0], issue: mockIssue2 });
|
||||
|
||||
expect(state.issuesByListId['gid://gitlab/List/1']).not.toContain(mockIssue2.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SET_CURRENT_PAGE', () => {
|
||||
expectNotImplemented(mutations.SET_CURRENT_PAGE);
|
||||
});
|
||||
|
|
|
@ -17,15 +17,15 @@ exports[`Design note component should match the snapshot 1`] = `
|
|||
/>
|
||||
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
class="gl-display-flex gl-justify-content-space-between"
|
||||
>
|
||||
<div>
|
||||
<a
|
||||
<gl-link-stub
|
||||
class="js-user-link"
|
||||
data-user-id="author-id"
|
||||
>
|
||||
<span
|
||||
class="note-header-author-name bold"
|
||||
class="note-header-author-name gl-font-weight-bold"
|
||||
>
|
||||
|
||||
</span>
|
||||
|
@ -37,7 +37,7 @@ exports[`Design note component should match the snapshot 1`] = `
|
|||
>
|
||||
@
|
||||
</span>
|
||||
</a>
|
||||
</gl-link-stub>
|
||||
|
||||
<span
|
||||
class="note-headline-light note-headline-meta"
|
||||
|
@ -46,7 +46,7 @@ exports[`Design note component should match the snapshot 1`] = `
|
|||
class="system-note-message"
|
||||
/>
|
||||
|
||||
<a
|
||||
<gl-link-stub
|
||||
class="note-timestamp system-note-separator gl-display-block gl-mb-2"
|
||||
href="#note_123"
|
||||
>
|
||||
|
@ -55,7 +55,7 @@ exports[`Design note component should match the snapshot 1`] = `
|
|||
time="2019-07-26T15:02:20Z"
|
||||
tooltipplacement="bottom"
|
||||
/>
|
||||
</a>
|
||||
</gl-link-stub>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -68,7 +68,7 @@ exports[`Design note component should match the snapshot 1`] = `
|
|||
</div>
|
||||
|
||||
<div
|
||||
class="note-text js-note-text md"
|
||||
class="note-text js-note-text"
|
||||
data-qa-selector="note_content"
|
||||
/>
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Design reply form component renders button text as "Comment" when creating a comment 1`] = `
|
||||
"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled\\">
|
||||
"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled gl-button\\">
|
||||
<!---->
|
||||
<!----> <span class=\\"gl-button-text\\">
|
||||
Comment
|
||||
</button>"
|
||||
</span></button>"
|
||||
`;
|
||||
|
||||
exports[`Design reply form component renders button text as "Save comment" when creating a comment 1`] = `
|
||||
"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled\\">
|
||||
"<button data-track-event=\\"click_button\\" data-qa-selector=\\"save_comment_button\\" type=\\"submit\\" disabled=\\"disabled\\" class=\\"btn btn-success btn-md disabled gl-button\\">
|
||||
<!---->
|
||||
<!----> <span class=\\"gl-button-text\\">
|
||||
Save comment
|
||||
</button>"
|
||||
</span></button>"
|
||||
`;
|
||||
|
|
|
@ -232,7 +232,7 @@ describe('Design discussions component', () => {
|
|||
{ discussionComment: 'test', isFormRendered: true },
|
||||
);
|
||||
|
||||
findReplyForm().vm.$emit('submitForm');
|
||||
findReplyForm().vm.$emit('submit-form');
|
||||
expect(mutate).toHaveBeenCalledWith(mutationVariables);
|
||||
|
||||
await mutate();
|
||||
|
@ -250,7 +250,7 @@ describe('Design discussions component', () => {
|
|||
return wrapper.vm
|
||||
.$nextTick()
|
||||
.then(() => {
|
||||
findReplyForm().vm.$emit('cancelForm');
|
||||
findReplyForm().vm.$emit('cancel-form');
|
||||
|
||||
expect(wrapper.vm.discussionComment).toBe('');
|
||||
return wrapper.vm.$nextTick();
|
||||
|
@ -321,6 +321,6 @@ describe('Design discussions component', () => {
|
|||
createComponent();
|
||||
findReplyPlaceholder().vm.$emit('onClick');
|
||||
|
||||
expect(wrapper.emitted('openForm')).toBeTruthy();
|
||||
expect(wrapper.emitted('open-form')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -133,8 +133,8 @@ describe('Design note component', () => {
|
|||
expect(findReplyForm().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('hides the form on hideForm event', () => {
|
||||
findReplyForm().vm.$emit('cancelForm');
|
||||
it('hides the form on cancel-form event', () => {
|
||||
findReplyForm().vm.$emit('cancel-form');
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findReplyForm().exists()).toBe(false);
|
||||
|
@ -142,8 +142,8 @@ describe('Design note component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls a mutation on submitForm event and hides a form', () => {
|
||||
findReplyForm().vm.$emit('submitForm');
|
||||
it('calls a mutation on submit-form event and hides a form', () => {
|
||||
findReplyForm().vm.$emit('submit-form');
|
||||
expect(mutate).toHaveBeenCalled();
|
||||
|
||||
return mutate()
|
||||
|
|
|
@ -70,7 +70,7 @@ describe('Design reply form component', () => {
|
|||
});
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.emitted('submitForm')).toBeFalsy();
|
||||
expect(wrapper.emitted('submit-form')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -80,20 +80,20 @@ describe('Design reply form component', () => {
|
|||
});
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.emitted('submitForm')).toBeFalsy();
|
||||
expect(wrapper.emitted('submit-form')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it('emits cancelForm event on pressing escape button on textarea', () => {
|
||||
findTextarea().trigger('keyup.esc');
|
||||
|
||||
expect(wrapper.emitted('cancelForm')).toBeTruthy();
|
||||
expect(wrapper.emitted('cancel-form')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('emits cancelForm event on clicking Cancel button', () => {
|
||||
findCancelButton().vm.$emit('click');
|
||||
|
||||
expect(wrapper.emitted('cancelForm')).toHaveLength(1);
|
||||
expect(wrapper.emitted('cancel-form')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -112,7 +112,7 @@ describe('Design reply form component', () => {
|
|||
findSubmitButton().vm.$emit('click');
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.emitted('submitForm')).toBeTruthy();
|
||||
expect(wrapper.emitted('submit-form')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -122,7 +122,7 @@ describe('Design reply form component', () => {
|
|||
});
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.emitted('submitForm')).toBeTruthy();
|
||||
expect(wrapper.emitted('submit-form')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -132,7 +132,7 @@ describe('Design reply form component', () => {
|
|||
});
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(wrapper.emitted('submitForm')).toBeTruthy();
|
||||
expect(wrapper.emitted('submit-form')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -147,7 +147,7 @@ describe('Design reply form component', () => {
|
|||
it('emits cancelForm event on Escape key if text was not changed', () => {
|
||||
findTextarea().trigger('keyup.esc');
|
||||
|
||||
expect(wrapper.emitted('cancelForm')).toBeTruthy();
|
||||
expect(wrapper.emitted('cancel-form')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('opens confirmation modal on Escape key when text has changed', () => {
|
||||
|
@ -162,7 +162,7 @@ describe('Design reply form component', () => {
|
|||
it('emits cancelForm event on Cancel button click if text was not changed', () => {
|
||||
findCancelButton().trigger('click');
|
||||
|
||||
expect(wrapper.emitted('cancelForm')).toBeTruthy();
|
||||
expect(wrapper.emitted('cancel-form')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('opens confirmation modal on Cancel button click when text has changed', () => {
|
||||
|
@ -178,7 +178,7 @@ describe('Design reply form component', () => {
|
|||
findTextarea().trigger('keyup.esc');
|
||||
findModal().vm.$emit('ok');
|
||||
|
||||
expect(wrapper.emitted('cancelForm')).toBeTruthy();
|
||||
expect(wrapper.emitted('cancel-form')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -154,22 +154,22 @@ describe('Design management design sidebar component', () => {
|
|||
});
|
||||
|
||||
it('emits correct event on discussion create note error', () => {
|
||||
findFirstDiscussion().vm.$emit('createNoteError', 'payload');
|
||||
findFirstDiscussion().vm.$emit('create-note-error', 'payload');
|
||||
expect(wrapper.emitted('onDesignDiscussionError')).toEqual([['payload']]);
|
||||
});
|
||||
|
||||
it('emits correct event on discussion update note error', () => {
|
||||
findFirstDiscussion().vm.$emit('updateNoteError', 'payload');
|
||||
findFirstDiscussion().vm.$emit('update-note-error', 'payload');
|
||||
expect(wrapper.emitted('updateNoteError')).toEqual([['payload']]);
|
||||
});
|
||||
|
||||
it('emits correct event on discussion resolve error', () => {
|
||||
findFirstDiscussion().vm.$emit('resolveDiscussionError', 'payload');
|
||||
findFirstDiscussion().vm.$emit('resolve-discussion-error', 'payload');
|
||||
expect(wrapper.emitted('resolveDiscussionError')).toEqual([['payload']]);
|
||||
});
|
||||
|
||||
it('changes prop correctly on opening discussion form', () => {
|
||||
findFirstDiscussion().vm.$emit('openForm', 'some-id');
|
||||
findFirstDiscussion().vm.$emit('open-form', 'some-id');
|
||||
|
||||
return wrapper.vm.$nextTick().then(() => {
|
||||
expect(findFirstDiscussion().props('discussionWithOpenForm')).toBe('some-id');
|
||||
|
|
|
@ -210,7 +210,7 @@ describe('Design management design index page', () => {
|
|||
},
|
||||
);
|
||||
|
||||
findDiscussionForm().vm.$emit('submitForm');
|
||||
findDiscussionForm().vm.$emit('submit-form');
|
||||
expect(mutate).toHaveBeenCalledWith(createDiscussionMutationVariables);
|
||||
|
||||
return wrapper.vm
|
||||
|
@ -235,7 +235,7 @@ describe('Design management design index page', () => {
|
|||
},
|
||||
);
|
||||
|
||||
findDiscussionForm().vm.$emit('cancelForm');
|
||||
findDiscussionForm().vm.$emit('cancel-form');
|
||||
|
||||
expect(wrapper.vm.comment).toBe('');
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
|
|||
>
|
||||
<textarea
|
||||
aria-label="Description"
|
||||
class="note-textarea js-gfm-input js-autosize markdown-area"
|
||||
class="note-textarea js-gfm-input js-autosize markdown-area js-gfm-input-initialized"
|
||||
data-qa-selector="snippet_description_field"
|
||||
data-supports-quick-actions="false"
|
||||
dir="auto"
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import { GlTable, GlLoadingIcon } from '@gitlab/ui';
|
||||
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
|
||||
|
||||
const mockAlert = {
|
||||
iid: '1527542',
|
||||
title: 'SyntaxError: Invalid or unexpected token',
|
||||
severity: 'CRITICAL',
|
||||
eventCount: 7,
|
||||
createdAt: '2020-04-17T23:18:14.996Z',
|
||||
startedAt: '2020-04-17T23:18:14.996Z',
|
||||
endedAt: '2020-04-17T23:18:14.996Z',
|
||||
status: 'TRIGGERED',
|
||||
assignees: { nodes: [] },
|
||||
notes: { nodes: [] },
|
||||
todos: { nodes: [] },
|
||||
};
|
||||
|
||||
describe('AlertDetails', () => {
|
||||
let wrapper;
|
||||
|
||||
function mountComponent(propsData = {}) {
|
||||
wrapper = mount(AlertDetailsTable, {
|
||||
propsData: {
|
||||
alert: mockAlert,
|
||||
loading: false,
|
||||
...propsData,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const findTableComponent = () => wrapper.find(GlTable);
|
||||
|
||||
describe('Alert details', () => {
|
||||
describe('empty state', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ alert: null });
|
||||
});
|
||||
|
||||
it('shows an empty state when no alert is provided', () => {
|
||||
expect(wrapper.text()).toContain('No alert data to display.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loading state', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ loading: true });
|
||||
});
|
||||
|
||||
it('displays a loading state when loading', () => {
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with table data', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent();
|
||||
});
|
||||
|
||||
it('renders a table', () => {
|
||||
expect(findTableComponent().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a cell based on alert data', () => {
|
||||
expect(findTableComponent().text()).toContain('SyntaxError: Invalid or unexpected token');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['CurrentUserTodos'] do
|
||||
specify { expect(described_class.graphql_name).to eq('CurrentUserTodos') }
|
||||
|
||||
specify { expect(described_class).to have_graphql_fields(:current_user_todos).only }
|
||||
end
|
|
@ -3,8 +3,10 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['Design'] do
|
||||
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
|
||||
|
||||
it_behaves_like 'a GraphQL type with design fields' do
|
||||
let(:extra_design_fields) { %i[notes discussions versions] }
|
||||
let(:extra_design_fields) { %i[notes current_user_todos discussions versions] }
|
||||
let_it_be(:design) { create(:design, :with_versions) }
|
||||
let(:object_id) { GitlabSchema.id_from_object(design) }
|
||||
let_it_be(:object_id_b) { GitlabSchema.id_from_object(create(:design, :with_versions)) }
|
||||
|
|
|
@ -11,11 +11,13 @@ RSpec.describe GitlabSchema.types['Issue'] do
|
|||
|
||||
specify { expect(described_class.interfaces).to include(Types::Notes::NoteableType) }
|
||||
|
||||
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
|
||||
|
||||
it 'has specific fields' do
|
||||
fields = %i[id iid title description state reference author assignees participants labels milestone due_date
|
||||
confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position
|
||||
subscribed time_estimate total_time_spent closed_at created_at updated_at task_completion_status
|
||||
designs design_collection alert_management_alert severity]
|
||||
designs design_collection alert_management_alert severity current_user_todos]
|
||||
|
||||
fields.each do |field_name|
|
||||
expect(described_class).to have_graphql_field(field_name)
|
||||
|
|
|
@ -9,6 +9,8 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
|
|||
|
||||
specify { expect(described_class.interfaces).to include(Types::Notes::NoteableType) }
|
||||
|
||||
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
|
||||
|
||||
it 'has the expected fields' do
|
||||
expected_fields = %w[
|
||||
notes discussions user_permissions id iid title title_html description
|
||||
|
@ -24,7 +26,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
|
|||
source_branch_exists target_branch_exists
|
||||
upvotes downvotes head_pipeline pipelines task_completion_status
|
||||
milestone assignees participants subscribed labels discussion_locked time_estimate
|
||||
total_time_spent reference author merged_at commit_count
|
||||
total_time_spent reference author merged_at commit_count current_user_todos
|
||||
]
|
||||
|
||||
if Gitlab.ee?
|
||||
|
|
|
@ -416,5 +416,28 @@ RSpec.describe Backup::Manager do
|
|||
subject.upload
|
||||
end
|
||||
end
|
||||
|
||||
context 'with AzureRM provider' do
|
||||
before do
|
||||
stub_backup_setting(
|
||||
upload: {
|
||||
connection: {
|
||||
provider: 'AzureRM',
|
||||
azure_storage_account_name: 'test-access-id',
|
||||
azure_storage_access_key: 'secret'
|
||||
},
|
||||
remote_directory: 'directory',
|
||||
multipart_chunk_size: nil,
|
||||
encryption: nil,
|
||||
encryption_key: nil,
|
||||
storage_class: nil
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'loads the provider' do
|
||||
expect { subject.upload }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::DataBuilder::Deployment do
|
||||
describe '.build' do
|
||||
it 'returns the object kind for a deployment' do
|
||||
deployment = build(:deployment)
|
||||
deployment = build(:deployment, deployable: nil, environment: create(:environment))
|
||||
|
||||
data = described_class.build(deployment)
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::UsageDataQueries do
|
||||
before do
|
||||
allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
|
||||
end
|
||||
|
||||
describe '.count' do
|
||||
it 'returns the raw SQL' do
|
||||
expect(described_class.count(User)).to start_with('SELECT COUNT("users"."id") FROM "users"')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.distinct_count' do
|
||||
it 'returns the raw SQL' do
|
||||
expect(described_class.distinct_count(Issue, :author_id)).to eq('SELECT COUNT(DISTINCT "issues"."author_id") FROM "issues"')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require 'fast_spec_helper'
|
||||
require 'rspec-parameterized'
|
||||
require 'fog/core'
|
||||
|
||||
RSpec.describe ObjectStorage::Config do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
@ -35,6 +36,46 @@ RSpec.describe ObjectStorage::Config do
|
|||
|
||||
subject { described_class.new(raw_config.as_json) }
|
||||
|
||||
describe '#load_provider' do
|
||||
before do
|
||||
subject.load_provider
|
||||
end
|
||||
|
||||
context 'with AWS' do
|
||||
it 'registers AWS as a provider' do
|
||||
expect(Fog.providers.keys).to include(:aws)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Google' do
|
||||
let(:credentials) do
|
||||
{
|
||||
provider: 'Google',
|
||||
google_storage_access_key_id: 'GOOGLE_ACCESS_KEY_ID',
|
||||
google_storage_secret_access_key: 'GOOGLE_SECRET_ACCESS_KEY'
|
||||
}
|
||||
end
|
||||
|
||||
it 'registers Google as a provider' do
|
||||
expect(Fog.providers.keys).to include(:google)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with Azure' do
|
||||
let(:credentials) do
|
||||
{
|
||||
provider: 'AzureRM',
|
||||
azure_storage_account_name: 'azuretest',
|
||||
azure_storage_access_key: 'ABCD1234'
|
||||
}
|
||||
end
|
||||
|
||||
it 'registers AzureRM as a provider' do
|
||||
expect(Fog.providers.keys).to include(:azurerm)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#credentials' do
|
||||
it { expect(subject.credentials).to eq(credentials) }
|
||||
end
|
||||
|
|
|
@ -61,7 +61,8 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
|
|||
end
|
||||
|
||||
describe 'namespace uniqueness validation' do
|
||||
let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') }
|
||||
let_it_be(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, cluster: cluster, namespace: 'my-namespace') }
|
||||
|
||||
subject { kubernetes_namespace }
|
||||
|
||||
|
|
|
@ -81,6 +81,8 @@ RSpec.describe Event do
|
|||
describe 'validations' do
|
||||
describe 'action' do
|
||||
context 'for a design' do
|
||||
let_it_be(:author) { create(:user) }
|
||||
|
||||
where(:action, :valid) do
|
||||
valid = described_class::DESIGN_ACTIONS.map(&:to_s).to_set
|
||||
|
||||
|
@ -90,7 +92,7 @@ RSpec.describe Event do
|
|||
end
|
||||
|
||||
with_them do
|
||||
let(:event) { build(:design_event, action: action) }
|
||||
let(:event) { build(:design_event, author: author, action: action) }
|
||||
|
||||
specify { expect(event.valid?).to eq(valid) }
|
||||
end
|
||||
|
@ -731,7 +733,8 @@ RSpec.describe Event do
|
|||
end
|
||||
|
||||
target = kind == :project ? nil : build(kind, **extra_data)
|
||||
[kind, build(:event, :created, project: project, target: target)]
|
||||
|
||||
[kind, build(:event, :created, author: project.owner, project: project, target: target)]
|
||||
end.to_h
|
||||
end
|
||||
|
||||
|
|
|
@ -29,23 +29,6 @@ RSpec.describe ChatMessage::MergeMessage do
|
|||
}
|
||||
end
|
||||
|
||||
# Integration point in EE
|
||||
context 'when state is overridden' do
|
||||
it 'respects the overridden state' do
|
||||
allow(subject).to receive(:state_or_action_text) { 'devoured' }
|
||||
|
||||
aggregate_failures do
|
||||
expect(subject.summary).not_to include('opened')
|
||||
expect(subject.summary).to include('devoured')
|
||||
|
||||
activity_title = subject.activity[:title]
|
||||
|
||||
expect(activity_title).not_to include('opened')
|
||||
expect(activity_title).to include('devoured')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without markdown' do
|
||||
let(:color) { '#345' }
|
||||
|
||||
|
@ -106,4 +89,56 @@ RSpec.describe ChatMessage::MergeMessage do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'approved' do
|
||||
before do
|
||||
args[:object_attributes][:action] = 'approved'
|
||||
end
|
||||
|
||||
it 'returns a message regarding completed approval of merge requests' do
|
||||
expect(subject.pretext).to eq(
|
||||
'Test User (test.user) approved merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> '\
|
||||
'in <http://somewhere.com|project_name>')
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'unapproved' do
|
||||
before do
|
||||
args[:object_attributes][:action] = 'unapproved'
|
||||
end
|
||||
|
||||
it 'returns a message regarding revocation of completed approval of merge requests' do
|
||||
expect(subject.pretext).to eq(
|
||||
'Test User (test.user) unapproved merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> '\
|
||||
'in <http://somewhere.com|project_name>')
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'approval' do
|
||||
before do
|
||||
args[:object_attributes][:action] = 'approval'
|
||||
end
|
||||
|
||||
it 'returns a message regarding added approval of merge requests' do
|
||||
expect(subject.pretext).to eq(
|
||||
'Test User (test.user) added their approval to merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> '\
|
||||
'in <http://somewhere.com|project_name>')
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'unapproval' do
|
||||
before do
|
||||
args[:object_attributes][:action] = 'unapproval'
|
||||
end
|
||||
|
||||
it 'returns a message regarding revoking approval of merge requests' do
|
||||
expect(subject.pretext).to eq(
|
||||
'Test User (test.user) removed their approval from merge request <http://somewhere.com/-/merge_requests/100|!100 *Merge Request title*> '\
|
||||
'in <http://somewhere.com|project_name>')
|
||||
expect(subject.attachments).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,19 +17,28 @@ RSpec.describe ProjectWiki do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#update_container_activity' do
|
||||
describe '#after_wiki_activity' do
|
||||
it 'updates project activity' do
|
||||
wiki_container.update!(
|
||||
last_activity_at: nil,
|
||||
last_repository_updated_at: nil
|
||||
)
|
||||
|
||||
subject.create_page('Test Page', 'This is content')
|
||||
subject.send(:after_wiki_activity)
|
||||
wiki_container.reload
|
||||
|
||||
expect(wiki_container.last_activity_at).to be_within(1.minute).of(Time.current)
|
||||
expect(wiki_container.last_repository_updated_at).to be_within(1.minute).of(Time.current)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#after_post_receive' do
|
||||
it 'updates project activity and expires caches' do
|
||||
expect(wiki).to receive(:after_wiki_activity)
|
||||
expect(ProjectCacheWorker).to receive(:perform_async).with(wiki_container.id, [], [:wiki_size])
|
||||
|
||||
subject.send(:after_post_receive)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ require 'spec_helper'
|
|||
RSpec.describe ProjectPolicy do
|
||||
include ExternalAuthorizationServiceHelpers
|
||||
include_context 'ProjectPolicy context'
|
||||
|
||||
let_it_be(:other_user) { create(:user) }
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user) }
|
||||
|
@ -14,78 +15,6 @@ RSpec.describe ProjectPolicy do
|
|||
let_it_be(:admin) { create(:admin) }
|
||||
let(:project) { create(:project, :public, namespace: owner.namespace) }
|
||||
|
||||
let(:base_guest_permissions) do
|
||||
%i[
|
||||
read_project read_board read_list read_wiki read_issue
|
||||
read_project_for_iids read_issue_iid read_label
|
||||
read_milestone read_snippet read_project_member read_note
|
||||
create_project create_issue create_note upload_file create_merge_request_in
|
||||
award_emoji read_release read_issue_link
|
||||
]
|
||||
end
|
||||
|
||||
let(:base_reporter_permissions) do
|
||||
%i[
|
||||
download_code fork_project create_snippet update_issue
|
||||
admin_issue admin_label admin_list read_commit_status read_build
|
||||
read_container_image read_pipeline read_environment read_deployment
|
||||
read_merge_request download_wiki_code read_sentry_issue read_metrics_dashboard_annotation
|
||||
metrics_dashboard read_confidential_issues admin_issue_link
|
||||
]
|
||||
end
|
||||
|
||||
let(:team_member_reporter_permissions) do
|
||||
%i[build_download_code build_read_container_image]
|
||||
end
|
||||
|
||||
let(:developer_permissions) do
|
||||
%i[
|
||||
admin_tag admin_milestone admin_merge_request update_merge_request create_commit_status
|
||||
update_commit_status create_build update_build create_pipeline
|
||||
update_pipeline create_merge_request_from create_wiki push_code
|
||||
resolve_note create_container_image update_container_image destroy_container_image daily_statistics
|
||||
create_environment update_environment create_deployment update_deployment create_release update_release
|
||||
create_metrics_dashboard_annotation delete_metrics_dashboard_annotation update_metrics_dashboard_annotation
|
||||
read_terraform_state read_pod_logs
|
||||
]
|
||||
end
|
||||
|
||||
let(:base_maintainer_permissions) do
|
||||
%i[
|
||||
push_to_delete_protected_branch update_snippet
|
||||
admin_snippet admin_project_member admin_note admin_wiki admin_project
|
||||
admin_commit_status admin_build admin_container_image
|
||||
admin_pipeline admin_environment admin_deployment destroy_release add_cluster
|
||||
read_deploy_token create_deploy_token destroy_deploy_token
|
||||
admin_terraform_state
|
||||
]
|
||||
end
|
||||
|
||||
let(:public_permissions) do
|
||||
%i[
|
||||
download_code fork_project read_commit_status read_pipeline
|
||||
read_container_image build_download_code build_read_container_image
|
||||
download_wiki_code read_release
|
||||
]
|
||||
end
|
||||
|
||||
let(:owner_permissions) do
|
||||
%i[
|
||||
change_namespace change_visibility_level rename_project remove_project
|
||||
archive_project remove_fork_project destroy_merge_request destroy_issue
|
||||
set_issue_iid set_issue_created_at set_issue_updated_at set_note_created_at
|
||||
]
|
||||
end
|
||||
|
||||
# Used in EE specs
|
||||
let(:additional_guest_permissions) { [] }
|
||||
let(:additional_reporter_permissions) { [] }
|
||||
let(:additional_maintainer_permissions) { [] }
|
||||
|
||||
let(:guest_permissions) { base_guest_permissions + additional_guest_permissions }
|
||||
let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions }
|
||||
let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions }
|
||||
|
||||
before do
|
||||
project.add_guest(guest)
|
||||
project.add_maintainer(maintainer)
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'A Todoable that implements the CurrentUserTodos interface' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:todoable) { create(:issue, project: project) }
|
||||
let_it_be(:done_todo) { create(:todo, state: :done, target: todoable, user: current_user) }
|
||||
let_it_be(:pending_todo) { create(:todo, state: :pending, target: todoable, user: current_user) }
|
||||
let(:state) { 'null' }
|
||||
|
||||
let(:todoable_response) do
|
||||
graphql_data_at(:project, :issue, :currentUserTodos, :nodes)
|
||||
end
|
||||
|
||||
let(:query) do
|
||||
<<~GQL
|
||||
{
|
||||
project(fullPath: "#{project.full_path}") {
|
||||
issue(iid: "#{todoable.iid}") {
|
||||
currentUserTodos(state: #{state}) {
|
||||
nodes {
|
||||
#{all_graphql_fields_for('Todo', max_depth: 1)}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GQL
|
||||
end
|
||||
|
||||
it 'returns todos of the current user' do
|
||||
post_graphql(query, current_user: current_user)
|
||||
|
||||
expect(todoable_response).to contain_exactly(
|
||||
a_hash_including('id' => global_id_of(done_todo)),
|
||||
a_hash_including('id' => global_id_of(pending_todo))
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not return todos of another user', :aggregate_failures do
|
||||
post_graphql(query, current_user: create(:user))
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(todoable_response).to be_empty
|
||||
end
|
||||
|
||||
it 'does not error when there is no logged in user', :aggregate_failures do
|
||||
post_graphql(query)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:success)
|
||||
expect(todoable_response).to be_empty
|
||||
end
|
||||
|
||||
context 'when `state` argument is `pending`' do
|
||||
let(:state) { 'pending' }
|
||||
|
||||
it 'returns just the pending todo' do
|
||||
post_graphql(query, current_user: current_user)
|
||||
|
||||
expect(todoable_response).to contain_exactly(
|
||||
a_hash_including('id' => global_id_of(pending_todo))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `state` argument is `done`' do
|
||||
let(:state) { 'done' }
|
||||
|
||||
it 'returns just the done todo' do
|
||||
post_graphql(query, current_user: current_user)
|
||||
|
||||
expect(todoable_response).to contain_exactly(
|
||||
a_hash_including('id' => global_id_of(done_todo))
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,12 +6,20 @@ RSpec.describe Git::WikiPushService, services: true do
|
|||
include RepoHelpers
|
||||
|
||||
let_it_be(:key_id) { create(:key, user: current_user).shell_id }
|
||||
let_it_be(:project) { create(:project, :wiki_repo) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:git_wiki) { project.wiki.wiki }
|
||||
let_it_be(:repository) { git_wiki.repository }
|
||||
let_it_be(:wiki) { create(:project_wiki) }
|
||||
let_it_be(:current_user) { wiki.container.default_owner }
|
||||
let_it_be(:git_wiki) { wiki.wiki }
|
||||
let_it_be(:repository) { wiki.repository }
|
||||
|
||||
describe '#execute' do
|
||||
it 'executes model-specific callbacks' do
|
||||
expect(wiki).to receive(:after_post_receive)
|
||||
|
||||
create_service(current_sha).execute
|
||||
end
|
||||
end
|
||||
|
||||
describe '#process_changes' do
|
||||
context 'the push contains more than the permitted number of changes' do
|
||||
def run_service
|
||||
process_changes { described_class::MAX_CHANGES.succ.times { write_new_page } }
|
||||
|
@ -37,8 +45,8 @@ RSpec.describe Git::WikiPushService, services: true do
|
|||
let(:count) { Event::WIKI_ACTIONS.size }
|
||||
|
||||
def run_service
|
||||
wiki_page_a = create(:wiki_page, project: project)
|
||||
wiki_page_b = create(:wiki_page, project: project)
|
||||
wiki_page_a = create(:wiki_page, wiki: wiki)
|
||||
wiki_page_b = create(:wiki_page, wiki: wiki)
|
||||
|
||||
process_changes do
|
||||
write_new_page
|
||||
|
@ -135,7 +143,7 @@ RSpec.describe Git::WikiPushService, services: true do
|
|||
end
|
||||
|
||||
context 'when a page we already know about has been updated' do
|
||||
let(:wiki_page) { create(:wiki_page, project: project) }
|
||||
let(:wiki_page) { create(:wiki_page, wiki: wiki) }
|
||||
|
||||
before do
|
||||
create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page)
|
||||
|
@ -165,7 +173,7 @@ RSpec.describe Git::WikiPushService, services: true do
|
|||
|
||||
context 'when a page we do not know about has been updated' do
|
||||
def run_service
|
||||
wiki_page = create(:wiki_page, project: project)
|
||||
wiki_page = create(:wiki_page, wiki: wiki)
|
||||
process_changes { update_page(wiki_page.title) }
|
||||
end
|
||||
|
||||
|
@ -189,7 +197,7 @@ RSpec.describe Git::WikiPushService, services: true do
|
|||
|
||||
context 'when a page we do not know about has been deleted' do
|
||||
def run_service
|
||||
wiki_page = create(:wiki_page, project: project)
|
||||
wiki_page = create(:wiki_page, wiki: wiki)
|
||||
process_changes { delete_page(wiki_page.page.path) }
|
||||
end
|
||||
|
||||
|
@ -254,9 +262,9 @@ RSpec.describe Git::WikiPushService, services: true do
|
|||
|
||||
it_behaves_like 'a no-op push'
|
||||
|
||||
context 'but is enabled for a given project' do
|
||||
context 'but is enabled for a given container' do
|
||||
before do
|
||||
stub_feature_flags(wiki_events_on_git_push: project)
|
||||
stub_feature_flags(wiki_events_on_git_push: wiki.container)
|
||||
end
|
||||
|
||||
it 'creates events' do
|
||||
|
@ -280,19 +288,19 @@ RSpec.describe Git::WikiPushService, services: true do
|
|||
|
||||
def create_service(base, refs = ['refs/heads/master'])
|
||||
changes = post_received(base, refs).changes
|
||||
described_class.new(project, current_user, changes: changes)
|
||||
described_class.new(wiki, current_user, changes: changes)
|
||||
end
|
||||
|
||||
def post_received(base, refs)
|
||||
change_str = refs.map { |ref| +"#{base} #{current_sha} #{ref}" }.join("\n")
|
||||
post_received = ::Gitlab::GitPostReceive.new(project, key_id, change_str, {})
|
||||
post_received = ::Gitlab::GitPostReceive.new(wiki.container, key_id, change_str, {})
|
||||
allow(post_received).to receive(:identify).with(key_id).and_return(current_user)
|
||||
|
||||
post_received
|
||||
end
|
||||
|
||||
def current_sha
|
||||
repository.gitaly_ref_client.find_branch('master')&.dereferenced_target&.id || Gitlab::Git::BLANK_SHA
|
||||
repository.commit('master')&.id || Gitlab::Git::BLANK_SHA
|
||||
end
|
||||
|
||||
# It is important not to re-use the WikiPage services here, since they create
|
||||
|
@ -312,7 +320,7 @@ RSpec.describe Git::WikiPushService, services: true do
|
|||
file_content: 'some stuff',
|
||||
branch_name: 'master'
|
||||
}
|
||||
::Wikis::CreateAttachmentService.new(container: project, current_user: project.owner, params: params).execute
|
||||
::Wikis::CreateAttachmentService.new(container: wiki.container, current_user: current_user, params: params).execute
|
||||
end
|
||||
|
||||
def update_page(title)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue