Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a7704bf16a
commit
7b2635a55d
|
@ -214,7 +214,6 @@ linters:
|
|||
- 'app/views/projects/mattermosts/_team_selection.html.haml'
|
||||
- 'app/views/projects/mattermosts/new.html.haml'
|
||||
- 'app/views/projects/merge_requests/_commits.html.haml'
|
||||
- 'app/views/projects/merge_requests/_how_to_merge.html.haml'
|
||||
- 'app/views/projects/merge_requests/_mr_title.html.haml'
|
||||
- 'app/views/projects/merge_requests/conflicts/_commit_stats.html.haml'
|
||||
- 'app/views/projects/merge_requests/conflicts/_file_actions.html.haml'
|
||||
|
|
|
@ -1 +1 @@
|
|||
9677be26e1a7f7e4f56bf4f7ba47bc0d16f40e16
|
||||
ae38755015839b67292e5f550a9a4fd72c3c1a56
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import {
|
||||
GlDropdownItem,
|
||||
|
@ -75,6 +75,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapGetters(['activeIssue']),
|
||||
...mapState(['isSettingAssignees']),
|
||||
assigneeText() {
|
||||
return n__('Assignee', '%d Assignees', this.selected.length);
|
||||
},
|
||||
|
@ -131,7 +132,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<board-editable-item :title="assigneeText" @close="saveAssignees">
|
||||
<board-editable-item :loading="isSettingAssignees" :title="assigneeText" @close="saveAssignees">
|
||||
<template #collapsed>
|
||||
<issuable-assignees :users="selected" @assign-self="assignSelf" />
|
||||
</template>
|
||||
|
|
|
@ -320,6 +320,8 @@ export default {
|
|||
},
|
||||
|
||||
setAssignees: ({ commit, getters }, assigneeUsernames) => {
|
||||
commit(types.SET_ASSIGNEE_LOADING, true);
|
||||
|
||||
return gqlClient
|
||||
.mutate({
|
||||
mutation: updateAssignees,
|
||||
|
@ -339,6 +341,9 @@ export default {
|
|||
});
|
||||
|
||||
return nodes;
|
||||
})
|
||||
.finally(() => {
|
||||
commit(types.SET_ASSIGNEE_LOADING, false);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -34,4 +34,5 @@ export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE';
|
|||
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
|
||||
export const SET_ACTIVE_ID = 'SET_ACTIVE_ID';
|
||||
export const UPDATE_ISSUE_BY_ID = 'UPDATE_ISSUE_BY_ID';
|
||||
export const SET_ASSIGNEE_LOADING = 'SET_ASSIGNEE_LOADING';
|
||||
export const RESET_ISSUES = 'RESET_ISSUES';
|
||||
|
|
|
@ -143,6 +143,10 @@ export default {
|
|||
Vue.set(state.issues[issueId], prop, value);
|
||||
},
|
||||
|
||||
[mutationTypes.SET_ASSIGNEE_LOADING](state, isLoading) {
|
||||
state.isSettingAssignees = isLoading;
|
||||
},
|
||||
|
||||
[mutationTypes.REQUEST_ADD_ISSUE]: () => {
|
||||
notImplemented();
|
||||
},
|
||||
|
|
|
@ -11,6 +11,7 @@ export default () => ({
|
|||
boardLists: {},
|
||||
listsFlags: {},
|
||||
issuesByListId: {},
|
||||
isSettingAssignees: false,
|
||||
pageInfoByListId: {},
|
||||
issues: {},
|
||||
filterParams: {},
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
import DropLab from './droplab/drop_lab';
|
||||
import ISetter from './droplab/plugins/input_setter';
|
||||
|
||||
// Todo: Remove this when fixing issue in input_setter plugin
|
||||
const InputSetter = { ...ISetter };
|
||||
|
||||
class CloseReopenReportToggle {
|
||||
constructor(opts = {}) {
|
||||
this.dropdownTrigger = opts.dropdownTrigger;
|
||||
this.dropdownList = opts.dropdownList;
|
||||
this.button = opts.button;
|
||||
}
|
||||
|
||||
initDroplab() {
|
||||
this.reopenItem = this.dropdownList.querySelector('.reopen-item');
|
||||
this.closeItem = this.dropdownList.querySelector('.close-item');
|
||||
|
||||
this.droplab = new DropLab();
|
||||
|
||||
const config = this.setConfig();
|
||||
|
||||
this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
|
||||
}
|
||||
|
||||
updateButton(isClosed) {
|
||||
this.toggleButtonType(isClosed);
|
||||
|
||||
this.button.blur();
|
||||
}
|
||||
|
||||
toggleButtonType(isClosed) {
|
||||
const [showItem, hideItem] = this.getButtonTypes(isClosed);
|
||||
|
||||
showItem.classList.remove('hidden');
|
||||
showItem.classList.add('droplab-item-selected');
|
||||
|
||||
hideItem.classList.add('hidden');
|
||||
hideItem.classList.remove('droplab-item-selected');
|
||||
|
||||
showItem.click();
|
||||
}
|
||||
|
||||
getButtonTypes(isClosed) {
|
||||
return isClosed ? [this.reopenItem, this.closeItem] : [this.closeItem, this.reopenItem];
|
||||
}
|
||||
|
||||
setDisable(shouldDisable = true) {
|
||||
if (shouldDisable) {
|
||||
this.button.setAttribute('disabled', 'true');
|
||||
this.dropdownTrigger.setAttribute('disabled', 'true');
|
||||
} else {
|
||||
this.button.removeAttribute('disabled');
|
||||
this.dropdownTrigger.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
setConfig() {
|
||||
const config = {
|
||||
InputSetter: [
|
||||
{
|
||||
input: this.button,
|
||||
valueAttribute: 'data-text',
|
||||
inputAttribute: 'data-value',
|
||||
},
|
||||
{
|
||||
input: this.button,
|
||||
valueAttribute: 'data-text',
|
||||
inputAttribute: 'title',
|
||||
},
|
||||
{
|
||||
input: this.button,
|
||||
valueAttribute: 'data-button-class',
|
||||
inputAttribute: 'class',
|
||||
},
|
||||
{
|
||||
input: this.dropdownTrigger,
|
||||
valueAttribute: 'data-toggle-class',
|
||||
inputAttribute: 'class',
|
||||
},
|
||||
{
|
||||
input: this.button,
|
||||
valueAttribute: 'data-url',
|
||||
inputAttribute: 'data-endpoint',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
export default CloseReopenReportToggle;
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlButton, GlAlert } from '@gitlab/ui';
|
||||
import { GlButton, GlAlert, GlModalDirective } from '@gitlab/ui';
|
||||
import { CENTERED_LIMITED_CONTAINER_CLASSES } from '../constants';
|
||||
|
||||
export default {
|
||||
|
@ -7,6 +7,9 @@ export default {
|
|||
GlAlert,
|
||||
GlButton,
|
||||
},
|
||||
directives: {
|
||||
GlModalDirective,
|
||||
},
|
||||
props: {
|
||||
limited: {
|
||||
type: Boolean,
|
||||
|
@ -60,9 +63,8 @@ export default {
|
|||
</gl-button>
|
||||
<gl-button
|
||||
v-if="mergeable"
|
||||
v-gl-modal-directive="'modal-merge-info'"
|
||||
class="gl-alert-action"
|
||||
data-toggle="modal"
|
||||
data-target="#modal_merge_info"
|
||||
>
|
||||
{{ __('Merge locally') }}
|
||||
</gl-button>
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import CloseReopenReportToggle from '../close_reopen_report_toggle';
|
||||
|
||||
function initCloseReopenReport() {
|
||||
const container = document.querySelector('.js-issuable-close-dropdown');
|
||||
|
||||
if (!container) return undefined;
|
||||
|
||||
const dropdownTrigger = container.querySelector('.js-issuable-close-toggle');
|
||||
const dropdownList = container.querySelector('.js-issuable-close-menu');
|
||||
const button = container.querySelector('.js-issuable-close-button');
|
||||
|
||||
const closeReopenReportToggle = new CloseReopenReportToggle({
|
||||
dropdownTrigger,
|
||||
dropdownList,
|
||||
button,
|
||||
});
|
||||
|
||||
closeReopenReportToggle.initDroplab();
|
||||
|
||||
return closeReopenReportToggle;
|
||||
}
|
||||
|
||||
const IssuablesHelper = {
|
||||
initCloseReopenReport,
|
||||
};
|
||||
|
||||
export default IssuablesHelper;
|
|
@ -1,15 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
export default () => {
|
||||
const modal = $('#modal_merge_info');
|
||||
|
||||
if (modal) {
|
||||
modal.modal({
|
||||
modal: true,
|
||||
show: false,
|
||||
});
|
||||
|
||||
$('.how_to_merge_link').on('click', modal.show);
|
||||
$('.modal-header .close').on('click', modal.hide);
|
||||
}
|
||||
};
|
|
@ -112,7 +112,7 @@ export default {
|
|||
</div>
|
||||
<div
|
||||
data-testid="header-actions"
|
||||
class="detail-page-header-actions js-issuable-actions js-issuable-buttons gl-display-flex gl-display-md-block"
|
||||
class="detail-page-header-actions gl-display-flex gl-display-md-block"
|
||||
>
|
||||
<slot name="header-actions"></slot>
|
||||
</div>
|
||||
|
|
|
@ -1,41 +1,22 @@
|
|||
/* eslint-disable consistent-return */
|
||||
|
||||
import $ from 'jquery';
|
||||
import axios from './lib/utils/axios_utils';
|
||||
import { addDelimiter } from './lib/utils/text_utility';
|
||||
import { deprecatedCreateFlash as flash } from './flash';
|
||||
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
|
||||
import IssuablesHelper from './helpers/issuables_helper';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { __ } from './locale';
|
||||
|
||||
export default class Issue {
|
||||
constructor() {
|
||||
if ($('.btn-close, .btn-reopen').length) this.initIssueBtnEventListeners();
|
||||
|
||||
if ($('.js-close-blocked-issue-warning').length) this.initIssueWarningBtnEventListener();
|
||||
|
||||
if ($('.js-alert-moved-from-service-desk-warning').length) {
|
||||
const trimmedPathname = window.location.pathname.slice(1);
|
||||
this.alertMovedFromServiceDeskDismissedKey = joinPaths(
|
||||
trimmedPathname,
|
||||
'alert-issue-moved-from-service-desk-dismissed',
|
||||
);
|
||||
|
||||
this.initIssueMovedFromServiceDeskDismissHandler();
|
||||
Issue.initIssueMovedFromServiceDeskDismissHandler();
|
||||
}
|
||||
|
||||
Issue.$btnNewBranch = $('#new-branch');
|
||||
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
|
||||
|
||||
if (document.querySelector('#related-branches')) {
|
||||
Issue.initRelatedBranches();
|
||||
}
|
||||
|
||||
this.closeButtons = $('.btn-close');
|
||||
this.reopenButtons = $('.btn-reopen');
|
||||
|
||||
this.initCloseReopenReport();
|
||||
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
|
||||
|
||||
if (Issue.createMrDropdownWrap) {
|
||||
this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap);
|
||||
|
@ -71,7 +52,6 @@ export default class Issue {
|
|||
isOpenBadge.toggleClass('hidden', isClosed);
|
||||
|
||||
$(document).trigger('issuable:change', isClosed);
|
||||
this.toggleCloseReopenButton(isClosed);
|
||||
|
||||
let numProjectIssues = Number(
|
||||
projectIssuesCounter
|
||||
|
@ -91,104 +71,16 @@ export default class Issue {
|
|||
}
|
||||
}
|
||||
|
||||
initIssueBtnEventListeners() {
|
||||
const issueFailMessage = __('Unable to update this issue at this time.');
|
||||
|
||||
$('.report-abuse-link').on('click', e => {
|
||||
// this is needed because of the implementation of
|
||||
// the dropdown toggle and Report Abuse needing to be
|
||||
// linked to another page.
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// NOTE: data attribute seems unnecessary but is actually necessary
|
||||
return $('.js-issuable-buttons[data-action="close-reopen"]').on(
|
||||
'click',
|
||||
'.btn-close, .btn-reopen, .btn-close-anyway',
|
||||
e => {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
const $button = $(e.currentTarget);
|
||||
const shouldSubmit = $button.hasClass('btn-comment');
|
||||
if (shouldSubmit) {
|
||||
Issue.submitNoteForm($button.closest('form'));
|
||||
}
|
||||
|
||||
const shouldDisplayBlockedWarning = $button.hasClass('btn-issue-blocked');
|
||||
const warningBanner = $('.js-close-blocked-issue-warning');
|
||||
if (shouldDisplayBlockedWarning) {
|
||||
this.toggleWarningAndCloseButton();
|
||||
} else {
|
||||
this.disableCloseReopenButton($button);
|
||||
|
||||
const url = $button.data('endpoint');
|
||||
|
||||
return axios
|
||||
.put(url)
|
||||
.then(({ data }) => {
|
||||
const isClosed = $button.is('.btn-close, .btn-close-anyway');
|
||||
this.updateTopState(isClosed, data);
|
||||
if ($button.hasClass('btn-close-anyway')) {
|
||||
warningBanner.addClass('hidden');
|
||||
if (this.closeReopenReportToggle)
|
||||
$('.js-issuable-close-dropdown').removeClass('hidden');
|
||||
}
|
||||
})
|
||||
.catch(() => flash(issueFailMessage))
|
||||
.then(() => {
|
||||
this.disableCloseReopenButton($button, false);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
initCloseReopenReport() {
|
||||
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
|
||||
|
||||
if (this.closeButtons) this.closeButtons = this.closeButtons.not('.issuable-close-button');
|
||||
if (this.reopenButtons) this.reopenButtons = this.reopenButtons.not('.issuable-close-button');
|
||||
}
|
||||
|
||||
disableCloseReopenButton($button, shouldDisable) {
|
||||
if (this.closeReopenReportToggle) {
|
||||
this.closeReopenReportToggle.setDisable(shouldDisable);
|
||||
} else {
|
||||
$button.prop('disabled', shouldDisable);
|
||||
}
|
||||
}
|
||||
|
||||
toggleCloseReopenButton(isClosed) {
|
||||
if (this.closeReopenReportToggle) this.closeReopenReportToggle.updateButton(isClosed);
|
||||
this.closeButtons.toggleClass('hidden', isClosed);
|
||||
this.reopenButtons.toggleClass('hidden', !isClosed);
|
||||
}
|
||||
|
||||
toggleWarningAndCloseButton() {
|
||||
const warningBanner = $('.js-close-blocked-issue-warning');
|
||||
warningBanner.toggleClass('hidden');
|
||||
$('.btn-close').toggleClass('hidden');
|
||||
if (this.closeReopenReportToggle) {
|
||||
$('.js-issuable-close-dropdown').toggleClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
initIssueWarningBtnEventListener() {
|
||||
return $(document).on(
|
||||
'click',
|
||||
'.js-close-blocked-issue-warning .js-cancel-blocked-issue-warning',
|
||||
e => {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
this.toggleWarningAndCloseButton();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
initIssueMovedFromServiceDeskDismissHandler() {
|
||||
static initIssueMovedFromServiceDeskDismissHandler() {
|
||||
const alertMovedFromServiceDeskWarning = $('.js-alert-moved-from-service-desk-warning');
|
||||
|
||||
if (!localStorage.getItem(this.alertMovedFromServiceDeskDismissedKey)) {
|
||||
const trimmedPathname = window.location.pathname.slice(1);
|
||||
const alertMovedFromServiceDeskDismissedKey = joinPaths(
|
||||
trimmedPathname,
|
||||
'alert-issue-moved-from-service-desk-dismissed',
|
||||
);
|
||||
|
||||
if (!localStorage.getItem(alertMovedFromServiceDeskDismissedKey)) {
|
||||
alertMovedFromServiceDeskWarning.show();
|
||||
}
|
||||
|
||||
|
@ -196,20 +88,13 @@ export default class Issue {
|
|||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
alertMovedFromServiceDeskWarning.remove();
|
||||
localStorage.setItem(this.alertMovedFromServiceDeskDismissedKey, true);
|
||||
localStorage.setItem(alertMovedFromServiceDeskDismissedKey, true);
|
||||
});
|
||||
}
|
||||
|
||||
static submitNoteForm(form) {
|
||||
const noteText = form.find('textarea.js-note-text').val();
|
||||
if (noteText && noteText.trim().length > 0) {
|
||||
return form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
static initRelatedBranches() {
|
||||
const $container = $('#related-branches');
|
||||
return axios
|
||||
axios
|
||||
.get($container.data('url'))
|
||||
.then(({ data }) => {
|
||||
if ('html' in data) {
|
||||
|
|
|
@ -2,7 +2,6 @@ import ZenMode from '~/zen_mode';
|
|||
import initIssuableSidebar from '~/init_issuable_sidebar';
|
||||
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
|
||||
import { handleLocationHash } from '~/lib/utils/common_utils';
|
||||
import howToMerge from '~/how_to_merge';
|
||||
import initPipelines from '~/commit/pipelines/pipelines_bundle';
|
||||
import initSourcegraph from '~/sourcegraph';
|
||||
import loadAwardsHandler from '~/awards_handler';
|
||||
|
@ -15,7 +14,6 @@ export default function() {
|
|||
initPipelines();
|
||||
new ShortcutsIssuable(true); // eslint-disable-line no-new
|
||||
handleLocationHash();
|
||||
howToMerge();
|
||||
initSourcegraph();
|
||||
loadAwardsHandler();
|
||||
initInviteMemberModal();
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<script>
|
||||
import { GlButton, GlModalDirective } from '@gitlab/ui';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
name: 'MRWidgetMergeHelp',
|
||||
components: {
|
||||
GlButton,
|
||||
},
|
||||
directives: {
|
||||
GlModalDirective,
|
||||
},
|
||||
props: {
|
||||
missingBranch: {
|
||||
type: String,
|
||||
|
@ -31,13 +38,12 @@ export default {
|
|||
{{ s__('mrWidget|You can merge this merge request manually using the') }}
|
||||
</template>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="btn-link btn-blank js-open-modal-help"
|
||||
data-toggle="modal"
|
||||
data-target="#modal_merge_info"
|
||||
<gl-button
|
||||
v-gl-modal-directive="'modal-merge-info'"
|
||||
variant="link"
|
||||
class="gl-mt-n2 js-open-modal-help"
|
||||
>
|
||||
{{ s__('mrWidget|command line') }}
|
||||
</button>
|
||||
</gl-button>
|
||||
</section>
|
||||
</template>
|
||||
|
|
|
@ -887,11 +887,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.issuable-close-button,
|
||||
.issuable-close-toggle {
|
||||
@include transition(border-color, color);
|
||||
}
|
||||
|
||||
.issuable-close-dropdown {
|
||||
.dropdown-menu {
|
||||
min-width: 270px;
|
||||
|
|
|
@ -507,19 +507,6 @@ $mr-widget-min-height: 69px;
|
|||
display: none;
|
||||
}
|
||||
|
||||
#modal_merge_info .modal-dialog {
|
||||
.dark {
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.btn-clipboard {
|
||||
margin-right: 20px;
|
||||
margin-top: 5px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mr-links {
|
||||
padding-left: $gl-padding-8 + $status-icon-size + $gl-btn-padding;
|
||||
|
||||
|
|
|
@ -73,6 +73,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
feature_category :service_desk, [:service_desk]
|
||||
feature_category :importers, [:import_csv, :export_csv]
|
||||
|
||||
attr_accessor :vulnerability_id
|
||||
|
||||
def index
|
||||
@issues = @issuables
|
||||
|
||||
|
@ -124,6 +126,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
service = ::Issues::CreateService.new(project, current_user, create_params)
|
||||
@issue = service.execute
|
||||
|
||||
create_vulnerability_issue_link(issue)
|
||||
|
||||
if service.discussions_to_resolve.count(&:resolved?) > 0
|
||||
flash[:notice] = if service.discussion_to_resolve_id
|
||||
_("Resolved 1 discussion.")
|
||||
|
@ -384,6 +388,9 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
def service_desk?
|
||||
action_name == 'service_desk'
|
||||
end
|
||||
|
||||
# Overridden in EE
|
||||
def create_vulnerability_issue_link(issue); end
|
||||
end
|
||||
|
||||
Projects::IssuesController.prepend_if_ee('EE::Projects::IssuesController')
|
||||
|
|
|
@ -87,8 +87,12 @@ module Repositories
|
|||
@project
|
||||
end
|
||||
|
||||
def repository_path
|
||||
@repository_path ||= params[:repository_path]
|
||||
end
|
||||
|
||||
def parse_repo_path
|
||||
@container, @project, @repo_type, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:repository_id]}")
|
||||
@container, @project, @repo_type, @redirected_path = Gitlab::RepoPath.parse(repository_path)
|
||||
end
|
||||
|
||||
def render_missing_personal_access_token
|
||||
|
|
|
@ -90,7 +90,6 @@ module Repositories
|
|||
def access
|
||||
@access ||= access_klass.new(access_actor, container, 'http',
|
||||
authentication_abilities: authentication_abilities,
|
||||
namespace_path: params[:namespace_id],
|
||||
repository_path: repository_path,
|
||||
redirected_path: redirected_path,
|
||||
auth_result_type: auth_result_type)
|
||||
|
@ -113,10 +112,6 @@ module Repositories
|
|||
@access_klass ||= repo_type.access_checker_class
|
||||
end
|
||||
|
||||
def repository_path
|
||||
@repository_path ||= params[:repository_id].sub(/\.git$/, '')
|
||||
end
|
||||
|
||||
def log_user_activity
|
||||
Users::ActivityService.new(user).execute
|
||||
end
|
||||
|
|
|
@ -33,6 +33,8 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
format.json do
|
||||
# In 13.8, this endpoint will be removed:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/289972
|
||||
load_events
|
||||
pager_json("events/_events", @events.count, events: @events)
|
||||
end
|
||||
|
@ -42,6 +44,11 @@ class UsersController < ApplicationController
|
|||
def activity
|
||||
respond_to do |format|
|
||||
format.html { render 'show' }
|
||||
|
||||
format.json do
|
||||
load_events
|
||||
pager_json("events/_events", @events.count, events: @events)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class GroupMembersFinder < UnionFinder
|
|||
group_members = group_members_list
|
||||
relations = []
|
||||
|
||||
return group_members if include_relations == [:direct]
|
||||
return filter_members(group_members) if include_relations == [:direct]
|
||||
|
||||
relations << group_members if include_relations.include?(:direct)
|
||||
|
||||
|
|
|
@ -38,10 +38,11 @@ module Types
|
|||
field :user,
|
||||
Types::UserType,
|
||||
null: false,
|
||||
description: 'The user who awarded the emoji',
|
||||
resolve: -> (award_emoji, _args, _context) {
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, award_emoji.user_id).find
|
||||
}
|
||||
description: 'The user who awarded the emoji'
|
||||
|
||||
def user
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,8 +19,7 @@ module Types
|
|||
field :label, Types::LabelType, null: true,
|
||||
description: 'Label of the list'
|
||||
field :collapsed, GraphQL::BOOLEAN_TYPE, null: true,
|
||||
description: 'Indicates if list is collapsed for this user',
|
||||
resolve: -> (list, _args, ctx) { list.collapsed?(ctx[:current_user]) }
|
||||
description: 'Indicates if list is collapsed for this user'
|
||||
field :issues_count, GraphQL::INT_TYPE, null: true,
|
||||
description: 'Count of issues in the list'
|
||||
|
||||
|
@ -32,6 +31,10 @@ module Types
|
|||
metadata[:size]
|
||||
end
|
||||
|
||||
def collapsed
|
||||
object.collapsed?(context[:current_user])
|
||||
end
|
||||
|
||||
def metadata
|
||||
strong_memoize(:metadata) do
|
||||
list = self.object
|
||||
|
|
|
@ -25,20 +25,21 @@ module Types
|
|||
description: 'Tooltip associated with the status',
|
||||
method: :status_tooltip
|
||||
field :action, Types::Ci::StatusActionType, null: true,
|
||||
description: 'Action information for the status. This includes method, button title, icon, path, and title',
|
||||
resolve: -> (obj, _args, _ctx) {
|
||||
if obj.has_action?
|
||||
description: 'Action information for the status. This includes method, button title, icon, path, and title'
|
||||
|
||||
def action
|
||||
if object.has_action?
|
||||
{
|
||||
button_title: obj.action_button_title,
|
||||
icon: obj.action_icon,
|
||||
method: obj.action_method,
|
||||
path: obj.action_path,
|
||||
title: obj.action_title
|
||||
button_title: object.action_button_title,
|
||||
icon: object.action_icon,
|
||||
method: object.action_method,
|
||||
path: object.action_path,
|
||||
title: object.action_title
|
||||
}
|
||||
else
|
||||
nil
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
# rubocop: enable Graphql/AuthorizeTypes
|
||||
end
|
||||
|
|
|
@ -13,8 +13,11 @@ module Types
|
|||
field :jobs, Ci::JobType.connection_type, null: true,
|
||||
description: 'Jobs in group'
|
||||
field :detailed_status, Types::Ci::DetailedStatusType, null: true,
|
||||
description: 'Detailed status of the group',
|
||||
resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) }
|
||||
description: 'Detailed status of the group'
|
||||
|
||||
def detailed_status
|
||||
object.detailed_status(context[:current_user])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,25 +7,26 @@ module Types
|
|||
graphql_name 'CiJob'
|
||||
|
||||
field :pipeline, Types::Ci::PipelineType, null: false,
|
||||
description: 'Pipeline the job belongs to',
|
||||
resolve: -> (build, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, build.pipeline_id).find }
|
||||
|
||||
description: 'Pipeline the job belongs to'
|
||||
field :name, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Name of the job'
|
||||
|
||||
field :needs, JobType.connection_type, null: true,
|
||||
description: 'Builds that must complete before the jobs run'
|
||||
|
||||
field :detailed_status, Types::Ci::DetailedStatusType, null: true,
|
||||
description: 'Detailed status of the job',
|
||||
resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) }
|
||||
|
||||
description: 'Detailed status of the job'
|
||||
field :scheduled_at, Types::TimeType, null: true,
|
||||
description: 'Schedule for the build'
|
||||
|
||||
field :artifacts, Types::Ci::JobArtifactType.connection_type, null: true,
|
||||
description: 'Artifacts generated by the job'
|
||||
|
||||
def pipeline
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, object.pipeline_id).find
|
||||
end
|
||||
|
||||
def detailed_status
|
||||
object.detailed_status(context[:current_user])
|
||||
end
|
||||
|
||||
def artifacts
|
||||
if object.is_a?(::Ci::Build)
|
||||
object.job_artifacts
|
||||
|
|
|
@ -27,8 +27,7 @@ module Types
|
|||
description: "Status of the pipeline (#{::Ci::Pipeline.all_state_names.compact.join(', ').upcase})"
|
||||
|
||||
field :detailed_status, Types::Ci::DetailedStatusType, null: false,
|
||||
description: 'Detailed status of the pipeline',
|
||||
resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) }
|
||||
description: 'Detailed status of the pipeline'
|
||||
|
||||
field :config_source, PipelineConfigSourceEnum, null: true,
|
||||
description: "Config source of the pipeline (#{::Enums::Ci::Pipeline.config_sources.keys.join(', ').upcase})"
|
||||
|
@ -60,8 +59,7 @@ module Types
|
|||
resolver: Resolvers::Ci::PipelineStagesResolver
|
||||
|
||||
field :user, Types::UserType, null: true,
|
||||
description: 'Pipeline user',
|
||||
resolve: -> (pipeline, _args, _context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, pipeline.user_id).find }
|
||||
description: 'Pipeline user'
|
||||
|
||||
field :retryable, GraphQL::BOOLEAN_TYPE,
|
||||
description: 'Specifies if a pipeline can be retried',
|
||||
|
@ -91,11 +89,22 @@ module Types
|
|||
method: :triggered_by_pipeline
|
||||
|
||||
field :path, GraphQL::STRING_TYPE, null: true,
|
||||
description: "Relative path to the pipeline's page",
|
||||
resolve: -> (obj, _args, _ctx) { ::Gitlab::Routing.url_helpers.project_pipeline_path(obj.project, obj) }
|
||||
description: "Relative path to the pipeline's page"
|
||||
|
||||
field :project, Types::ProjectType, null: true,
|
||||
description: 'Project the pipeline belongs to'
|
||||
|
||||
def detailed_status
|
||||
object.detailed_status(context[:current_user])
|
||||
end
|
||||
|
||||
def user
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.user_id).find
|
||||
end
|
||||
|
||||
def path
|
||||
::Gitlab::Routing.url_helpers.project_pipeline_path(object.project, object)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,8 +11,11 @@ module Types
|
|||
field :groups, Ci::GroupType.connection_type, null: true,
|
||||
description: 'Group of jobs for the stage'
|
||||
field :detailed_status, Types::Ci::DetailedStatusType, null: true,
|
||||
description: 'Detailed status of the stage',
|
||||
resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) }
|
||||
description: 'Detailed status of the stage'
|
||||
|
||||
def detailed_status
|
||||
object.detailed_status(context[:current_user])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,10 +31,7 @@ module Types
|
|||
field :author_name, type: GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Commit authors name'
|
||||
field :author_gravatar, type: GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Commit authors gravatar',
|
||||
resolve: -> (commit, args, context) do
|
||||
GravatarService.new.execute(commit.author_email, 40)
|
||||
end
|
||||
description: 'Commit authors gravatar'
|
||||
|
||||
# models/commit lazy loads the author by email
|
||||
field :author, type: Types::UserType, null: true,
|
||||
|
@ -44,5 +41,9 @@ module Types
|
|||
null: true,
|
||||
description: 'Pipelines of the commit ordered latest first',
|
||||
resolver: Resolvers::CommitPipelinesResolver
|
||||
|
||||
def author_gravatar
|
||||
GravatarService.new.execute(object.author_email, 40)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,10 @@ module Types
|
|||
description 'Represents a Group Invitation'
|
||||
|
||||
field :group, Types::GroupType, null: true,
|
||||
description: 'Group that a User is invited to',
|
||||
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.source_id).find }
|
||||
description: 'Group that a User is invited to'
|
||||
|
||||
def group
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.source_id).find
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,10 @@ module Types
|
|||
description 'Represents a Group Membership'
|
||||
|
||||
field :group, Types::GroupType, null: true,
|
||||
description: 'Group that a User is a member of',
|
||||
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.source_id).find }
|
||||
description: 'Group that a User is a member of'
|
||||
|
||||
def group
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.source_id).find
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,10 +12,7 @@ module Types
|
|||
description: 'Web URL of the group'
|
||||
|
||||
field :avatar_url, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Avatar URL of the group',
|
||||
resolve: -> (group, args, ctx) do
|
||||
group.avatar_url(only_path: false)
|
||||
end
|
||||
description: 'Avatar URL of the group'
|
||||
|
||||
field :custom_emoji, Types::CustomEmojiType.connection_type, null: true,
|
||||
description: 'Custom emoji within this namespace',
|
||||
|
@ -44,8 +41,7 @@ module Types
|
|||
description: 'Indicates if a group is disabled from getting mentioned'
|
||||
|
||||
field :parent, GroupType, null: true,
|
||||
description: 'Parent group',
|
||||
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
|
||||
description: 'Parent group'
|
||||
|
||||
field :issues,
|
||||
Types::IssueType.connection_type,
|
||||
|
@ -120,6 +116,14 @@ module Types
|
|||
.execute
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
object.avatar_url(only_path: false)
|
||||
end
|
||||
|
||||
def parent
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.parent_id).find
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group
|
||||
|
|
|
@ -132,8 +132,7 @@ module Types
|
|||
description: 'Labels of the merge request'
|
||||
field :discussion_locked, GraphQL::BOOLEAN_TYPE,
|
||||
description: 'Indicates if comments on the merge request are locked to members only',
|
||||
null: false,
|
||||
resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked }
|
||||
null: false
|
||||
field :time_estimate, GraphQL::INT_TYPE, null: false,
|
||||
description: 'Time estimate of the merge request'
|
||||
field :total_time_spent, GraphQL::INT_TYPE, null: false,
|
||||
|
@ -200,6 +199,11 @@ module Types
|
|||
def source_branch_protected
|
||||
object.source_project.present? && ProtectedBranch.protected?(object.source_project, object.source_branch)
|
||||
end
|
||||
|
||||
def discussion_locked
|
||||
!!object.discussion_locked
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Types::MergeRequestType.prepend_if_ee('::EE::Types::MergeRequestType')
|
||||
|
|
|
@ -21,6 +21,7 @@ module Types
|
|||
field :description, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Description of the namespace'
|
||||
markdown_field :description_html, null: true
|
||||
|
||||
field :visibility, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Visibility of the namespace'
|
||||
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?,
|
||||
|
@ -30,12 +31,15 @@ module Types
|
|||
|
||||
field :root_storage_statistics, Types::RootStorageStatisticsType,
|
||||
null: true,
|
||||
description: 'Aggregated storage statistics of the namespace. Only available for root namespaces',
|
||||
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(obj.id).find }
|
||||
description: 'Aggregated storage statistics of the namespace. Only available for root namespaces'
|
||||
|
||||
field :projects, Types::ProjectType.connection_type, null: false,
|
||||
description: 'Projects within this namespace',
|
||||
resolver: ::Resolvers::NamespaceProjectsResolver
|
||||
|
||||
def root_storage_statistics
|
||||
Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(object.id).find
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -21,25 +21,43 @@ module Types
|
|||
|
||||
# Fields for text positions
|
||||
field :old_line, GraphQL::INT_TYPE, null: true,
|
||||
description: 'Line on start SHA that was changed',
|
||||
resolve: -> (position, _args, _ctx) { position.old_line if position.on_text? }
|
||||
description: 'Line on start SHA that was changed'
|
||||
field :new_line, GraphQL::INT_TYPE, null: true,
|
||||
description: 'Line on HEAD SHA that was changed',
|
||||
resolve: -> (position, _args, _ctx) { position.new_line if position.on_text? }
|
||||
description: 'Line on HEAD SHA that was changed'
|
||||
|
||||
# Fields for image positions
|
||||
field :x, GraphQL::INT_TYPE, null: true,
|
||||
description: 'X position of the note',
|
||||
resolve: -> (position, _args, _ctx) { position.x if position.on_image? }
|
||||
description: 'X position of the note'
|
||||
field :y, GraphQL::INT_TYPE, null: true,
|
||||
description: 'Y position of the note',
|
||||
resolve: -> (position, _args, _ctx) { position.y if position.on_image? }
|
||||
description: 'Y position of the note'
|
||||
field :width, GraphQL::INT_TYPE, null: true,
|
||||
description: 'Total width of the image',
|
||||
resolve: -> (position, _args, _ctx) { position.width if position.on_image? }
|
||||
description: 'Total width of the image'
|
||||
field :height, GraphQL::INT_TYPE, null: true,
|
||||
description: 'Total height of the image',
|
||||
resolve: -> (position, _args, _ctx) { position.height if position.on_image? }
|
||||
description: 'Total height of the image'
|
||||
|
||||
def old_line
|
||||
object.old_line if object.on_text?
|
||||
end
|
||||
|
||||
def new_line
|
||||
object.new_line if object.on_text?
|
||||
end
|
||||
|
||||
def x
|
||||
object.x if object.on_image?
|
||||
end
|
||||
|
||||
def y
|
||||
object.y if object.on_image?
|
||||
end
|
||||
|
||||
def width
|
||||
object.width if object.on_image?
|
||||
end
|
||||
|
||||
def height
|
||||
object.height if object.on_image?
|
||||
end
|
||||
end
|
||||
# rubocop: enable Graphql/AuthorizeTypes
|
||||
end
|
||||
|
|
|
@ -16,13 +16,11 @@ module Types
|
|||
|
||||
field :project, Types::ProjectType,
|
||||
null: true,
|
||||
description: 'Project associated with the note',
|
||||
resolve: -> (note, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, note.project_id).find }
|
||||
description: 'Project associated with the note'
|
||||
|
||||
field :author, Types::UserType,
|
||||
null: false,
|
||||
description: 'User who wrote this note',
|
||||
resolve: -> (note, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, note.author_id).find }
|
||||
description: 'User who wrote this note'
|
||||
|
||||
field :system, GraphQL::BOOLEAN_TYPE,
|
||||
null: false,
|
||||
|
@ -52,6 +50,14 @@ module Types
|
|||
def system_note_icon_name
|
||||
SystemNoteHelper.system_note_icon_name(object) if object.system?
|
||||
end
|
||||
|
||||
def project
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
|
||||
end
|
||||
|
||||
def author
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,9 @@ module Types
|
|||
permission_field field_name, method: :"can_#{field_name}?", calls_gitaly: true
|
||||
end
|
||||
|
||||
permission_field :can_merge, calls_gitaly: true, resolve: -> (object, args, context) do
|
||||
permission_field :can_merge, calls_gitaly: true
|
||||
|
||||
def can_merge
|
||||
object.can_be_merged_by?(context[:current_user])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,33 +67,25 @@ module Types
|
|||
description: 'E-mail address of the service desk.'
|
||||
|
||||
field :avatar_url, GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
|
||||
description: 'URL to avatar image file of the project',
|
||||
resolve: -> (project, args, ctx) do
|
||||
project.avatar_url(only_path: false)
|
||||
end
|
||||
description: 'URL to avatar image file of the project'
|
||||
|
||||
%i[issues merge_requests wiki snippets].each do |feature|
|
||||
field "#{feature}_enabled", GraphQL::BOOLEAN_TYPE, null: true,
|
||||
description: "Indicates if #{feature.to_s.titleize.pluralize} are enabled for the current user",
|
||||
resolve: -> (project, args, ctx) do
|
||||
project.feature_available?(feature, ctx[:current_user])
|
||||
description: "Indicates if #{feature.to_s.titleize.pluralize} are enabled for the current user"
|
||||
|
||||
define_method "#{feature}_enabled" do
|
||||
object.feature_available?(feature, context[:current_user])
|
||||
end
|
||||
end
|
||||
|
||||
field :jobs_enabled, GraphQL::BOOLEAN_TYPE, null: true,
|
||||
description: 'Indicates if CI/CD pipeline jobs are enabled for the current user',
|
||||
resolve: -> (project, args, ctx) do
|
||||
project.feature_available?(:builds, ctx[:current_user])
|
||||
end
|
||||
description: 'Indicates if CI/CD pipeline jobs are enabled for the current user'
|
||||
|
||||
field :public_jobs, GraphQL::BOOLEAN_TYPE, method: :public_builds, null: true,
|
||||
description: 'Indicates if there is public access to pipelines and job details of the project, including output logs and artifacts'
|
||||
|
||||
field :open_issues_count, GraphQL::INT_TYPE, null: true,
|
||||
description: 'Number of open issues for the project',
|
||||
resolve: -> (project, args, ctx) do
|
||||
project.open_issues_count if project.feature_available?(:issues, ctx[:current_user])
|
||||
end
|
||||
description: 'Number of open issues for the project'
|
||||
|
||||
field :import_status, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Status of import background job of the project'
|
||||
|
@ -123,8 +115,7 @@ module Types
|
|||
|
||||
field :statistics, Types::ProjectStatisticsType,
|
||||
null: true,
|
||||
description: 'Statistics of the project',
|
||||
resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(obj.id).find }
|
||||
description: 'Statistics of the project'
|
||||
|
||||
field :repository, Types::RepositoryType, null: true,
|
||||
description: 'Git repository of the project'
|
||||
|
@ -334,6 +325,22 @@ module Types
|
|||
.execute
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
object.avatar_url(only_path: false)
|
||||
end
|
||||
|
||||
def jobs_enabled
|
||||
object.feature_available?(:builds, context[:current_user])
|
||||
end
|
||||
|
||||
def open_issues_count
|
||||
object.open_issues_count if object.feature_available?(:issues, context[:current_user])
|
||||
end
|
||||
|
||||
def statistics
|
||||
Gitlab::Graphql::Loaders::BatchProjectStatisticsLoader.new(object.id).find
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project
|
||||
|
|
|
@ -24,7 +24,6 @@ module Types
|
|||
|
||||
field :current_user, Types::UserType,
|
||||
null: true,
|
||||
resolve: -> (_obj, _args, context) { context[:current_user] },
|
||||
description: "Get information about current user"
|
||||
|
||||
field :namespace, Types::NamespaceType,
|
||||
|
@ -116,6 +115,10 @@ module Types
|
|||
id = ::Types::GlobalIDType[::ContainerRepository].coerce_isolated_input(id)
|
||||
GitlabSchema.find_by_gid(id)
|
||||
end
|
||||
|
||||
def current_user
|
||||
context[:current_user]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -17,14 +17,12 @@ module Types
|
|||
field :collapsed, GraphQL::BOOLEAN_TYPE,
|
||||
description: 'Shows whether the blob should be displayed collapsed',
|
||||
method: :collapsed?,
|
||||
null: false,
|
||||
resolve: -> (viewer, _args, _ctx) { !!viewer&.collapsed? }
|
||||
null: false
|
||||
|
||||
field :too_large, GraphQL::BOOLEAN_TYPE,
|
||||
description: 'Shows whether the blob too large to be displayed',
|
||||
method: :too_large?,
|
||||
null: false,
|
||||
resolve: -> (viewer, _args, _ctx) { !!viewer&.too_large? }
|
||||
null: false
|
||||
|
||||
field :render_error, GraphQL::STRING_TYPE,
|
||||
description: 'Error rendering the blob content',
|
||||
|
@ -38,6 +36,14 @@ module Types
|
|||
field :loading_partial_name, GraphQL::STRING_TYPE,
|
||||
description: 'Loading partial name',
|
||||
null: false
|
||||
|
||||
def collapsed
|
||||
!!object&.collapsed?
|
||||
end
|
||||
|
||||
def too_large
|
||||
!!object&.too_large?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,8 +20,7 @@ module Types
|
|||
field :locked_by_user, Types::UserType,
|
||||
null: true,
|
||||
authorize: :read_user,
|
||||
description: 'The user currently holding a lock on the Terraform state',
|
||||
resolve: -> (state, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, state.locked_by_user_id).find }
|
||||
description: 'The user currently holding a lock on the Terraform state'
|
||||
|
||||
field :locked_at, Types::TimeType,
|
||||
null: true,
|
||||
|
@ -39,6 +38,10 @@ module Types
|
|||
field :updated_at, Types::TimeType,
|
||||
null: false,
|
||||
description: 'Timestamp the Terraform state was updated'
|
||||
|
||||
def locked_by_user
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.locked_by_user_id).find
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,14 +14,12 @@ module Types
|
|||
field :created_by_user, Types::UserType,
|
||||
null: true,
|
||||
authorize: :read_user,
|
||||
description: 'The user that created this version',
|
||||
resolve: -> (version, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, version.created_by_user_id).find }
|
||||
description: 'The user that created this version'
|
||||
|
||||
field :job, Types::Ci::JobType,
|
||||
null: true,
|
||||
authorize: :read_build,
|
||||
description: 'The job that created this version',
|
||||
resolve: -> (version, _, _) { Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Build, version.ci_build_id).find }
|
||||
description: 'The job that created this version'
|
||||
|
||||
field :created_at, Types::TimeType,
|
||||
null: false,
|
||||
|
@ -30,6 +28,14 @@ module Types
|
|||
field :updated_at, Types::TimeType,
|
||||
null: false,
|
||||
description: 'Timestamp the version was updated'
|
||||
|
||||
def created_by_user
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.created_by_user_id).find
|
||||
end
|
||||
|
||||
def job
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Build, object.ci_build_id).find
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,19 +16,16 @@ module Types
|
|||
field :project, Types::ProjectType,
|
||||
description: 'The project this todo is associated with',
|
||||
null: true,
|
||||
authorize: :read_project,
|
||||
resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, todo.project_id).find }
|
||||
authorize: :read_project
|
||||
|
||||
field :group, Types::GroupType,
|
||||
description: 'Group this todo is associated with',
|
||||
null: true,
|
||||
authorize: :read_group,
|
||||
resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, todo.group_id).find }
|
||||
authorize: :read_group
|
||||
|
||||
field :author, Types::UserType,
|
||||
description: 'The author of this todo',
|
||||
null: false,
|
||||
resolve: -> (todo, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, todo.author_id).find }
|
||||
null: false
|
||||
|
||||
field :action, Types::TodoActionEnum,
|
||||
description: 'Action of the todo',
|
||||
|
@ -50,5 +47,17 @@ module Types
|
|||
field :created_at, Types::TimeType,
|
||||
description: 'Timestamp this todo was created',
|
||||
null: false
|
||||
|
||||
def project
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
|
||||
end
|
||||
|
||||
def group
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, object.group_id).find
|
||||
end
|
||||
|
||||
def author
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,13 +15,14 @@ module Types
|
|||
field :web_path, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Web path of the blob'
|
||||
field :lfs_oid, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'LFS ID of the blob',
|
||||
resolve: -> (blob, args, ctx) do
|
||||
Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(blob.repository, blob.id).find
|
||||
end
|
||||
description: 'LFS ID of the blob'
|
||||
field :mode, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Blob mode in numeric format'
|
||||
|
||||
def lfs_oid
|
||||
Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(object.repository, object.id).find
|
||||
end
|
||||
end
|
||||
# rubocop: enable Graphql/AuthorizeTypes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,23 +12,28 @@ module Types
|
|||
description: 'Last commit for the tree'
|
||||
|
||||
field :trees, Types::Tree::TreeEntryType.connection_type, null: false,
|
||||
description: 'Trees of the tree',
|
||||
resolve: -> (obj, args, ctx) do
|
||||
Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository)
|
||||
end
|
||||
description: 'Trees of the tree'
|
||||
|
||||
field :submodules, Types::Tree::SubmoduleType.connection_type, null: false,
|
||||
description: 'Sub-modules of the tree',
|
||||
calls_gitaly: true, resolve: -> (obj, args, ctx) do
|
||||
Gitlab::Graphql::Representation::SubmoduleTreeEntry.decorate(obj.submodules, obj)
|
||||
end
|
||||
calls_gitaly: true
|
||||
|
||||
field :blobs, Types::Tree::BlobType.connection_type, null: false,
|
||||
description: 'Blobs of the tree',
|
||||
calls_gitaly: true, resolve: -> (obj, args, ctx) do
|
||||
Gitlab::Graphql::Representation::TreeEntry.decorate(obj.blobs, obj.repository)
|
||||
calls_gitaly: true
|
||||
|
||||
def trees
|
||||
Gitlab::Graphql::Representation::TreeEntry.decorate(object.trees, object.repository)
|
||||
end
|
||||
|
||||
def submodules
|
||||
Gitlab::Graphql::Representation::SubmoduleTreeEntry.decorate(object.submodules, object)
|
||||
end
|
||||
|
||||
def blobs
|
||||
Gitlab::Graphql::Representation::TreeEntry.decorate(object.blobs, object.repository)
|
||||
end
|
||||
end
|
||||
# rubocop: enable Graphql/AuthorizeTypes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
module Analytics::DevopsAdoption
|
||||
def self.table_name_prefix
|
||||
'analytics_devops_adoption_'
|
||||
end
|
||||
end
|
|
@ -1,24 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Analytics::DevopsAdoption::Segment < ApplicationRecord
|
||||
ALLOWED_SEGMENT_COUNT = 20
|
||||
|
||||
has_many :segment_selections
|
||||
has_many :groups, through: :segment_selections
|
||||
|
||||
validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
|
||||
validate :validate_segment_count, on: :create
|
||||
|
||||
accepts_nested_attributes_for :segment_selections, allow_destroy: true
|
||||
|
||||
scope :ordered_by_name, -> { order(:name) }
|
||||
scope :with_groups, -> { preload(:groups) }
|
||||
|
||||
private
|
||||
|
||||
def validate_segment_count
|
||||
if self.class.count >= ALLOWED_SEGMENT_COUNT
|
||||
errors.add(:name, s_('DevopsAdoptionSegment|The maximum number of segments has been reached'))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,36 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Analytics::DevopsAdoption::SegmentSelection < ApplicationRecord
|
||||
ALLOWED_SELECTIONS_PER_SEGMENT = 20
|
||||
|
||||
belongs_to :segment
|
||||
belongs_to :project
|
||||
belongs_to :group
|
||||
|
||||
validates :segment, presence: true
|
||||
validates :project, presence: { unless: :group }
|
||||
validates :project_id, uniqueness: { scope: :segment_id, if: :project }
|
||||
validates :group, presence: { unless: :project }
|
||||
validates :group_id, uniqueness: { scope: :segment_id, if: :group }
|
||||
|
||||
validate :exclusive_project_or_group
|
||||
validate :validate_selection_count, on: :create
|
||||
|
||||
private
|
||||
|
||||
def exclusive_project_or_group
|
||||
if project.present? && group.present?
|
||||
errors.add(:group, s_('DevopsAdoptionSegmentSelection|The selection cannot be configured for a project and for a group at the same time'))
|
||||
end
|
||||
end
|
||||
|
||||
def validate_selection_count
|
||||
return unless segment
|
||||
|
||||
# handle single model creation and bulk creation from accepts_nested_attributes_for
|
||||
selections = segment.segment_selections + [self]
|
||||
if selections.reject(&:marked_for_destruction?).uniq.size > ALLOWED_SELECTIONS_PER_SEGMENT
|
||||
errors.add(:segment, s_('DevopsAdoptionSegmentSelection|The maximum number of selections has been reached'))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,9 +31,8 @@ module Import
|
|||
project_name,
|
||||
target_namespace,
|
||||
current_user,
|
||||
access_params,
|
||||
type: provider
|
||||
).execute(extra_project_attrs)
|
||||
type: provider,
|
||||
**access_params).execute(extra_project_attrs)
|
||||
end
|
||||
|
||||
def repo
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
%button.dropdown-menu-toggle.dropdown-menu-full-width{ type: 'button', disabled: true }
|
||||
%span.dropdown-toggle-text
|
||||
= _('Select project')
|
||||
= icon('chevron-down')
|
||||
= sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3')
|
||||
%span.form-text.text-muted
|
||||
|
||||
.form-group
|
||||
|
@ -43,7 +43,7 @@
|
|||
%button.dropdown-menu-toggle.dropdown-menu-full-width{ type: 'button', disabled: true }
|
||||
%span.dropdown-toggle-text
|
||||
= _('Select project to choose zone')
|
||||
= icon('chevron-down')
|
||||
= sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3')
|
||||
%p.form-text.text-muted
|
||||
= s_('ClusterIntegration|Learn more about %{help_link_start}zones%{help_link_end}.').html_safe % { help_link_start: help_link_start % { url: zones_link_url }, help_link_end: help_link_end }
|
||||
|
||||
|
@ -59,7 +59,7 @@
|
|||
%button.dropdown-menu-toggle.dropdown-menu-full-width{ type: 'button', disabled: true }
|
||||
%span.dropdown-toggle-text
|
||||
= _('Select project and zone to choose machine type')
|
||||
= icon('chevron-down')
|
||||
= sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3')
|
||||
%p.form-text.text-muted
|
||||
= s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end }
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
= javascript_include_tag 'https://connect-cdn.atl-paas.net/all.js'
|
||||
= javascript_include_tag 'https://unpkg.com/jquery@3.3.1/dist/jquery.min.js'
|
||||
= Gon::Base.render_data(nonce: content_security_policy_nonce)
|
||||
= yield :head
|
||||
%body
|
||||
.ac-content
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
- if issuable.relocation_target
|
||||
- page_canonical_link issuable.relocation_target.present(current_user: current_user).web_url
|
||||
|
||||
= render_if_exists "projects/issues/alert_blocked", issue: issuable, current_user: current_user
|
||||
= render "projects/issues/alert_moved_from_service_desk", issue: issuable
|
||||
|
||||
= render 'shared/issue_type/details_header', issuable: issuable
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
#modal_merge_info.modal{ tabindex: '-1' }
|
||||
.modal-dialog.modal-lg
|
||||
.modal-content
|
||||
.modal-header
|
||||
%h3.modal-title Check out, review, and merge locally
|
||||
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
|
||||
%span{ "aria-hidden": true } ×
|
||||
.modal-body
|
||||
%p
|
||||
%strong Step 1.
|
||||
Fetch and check out the branch for this merge request
|
||||
= clipboard_button(target: "pre#merge-info-1", title: _("Copy commands"))
|
||||
%pre.dark#merge-info-1
|
||||
- if @merge_request.for_fork?
|
||||
-# All repo/branch refs have been quoted to allow support for special characters (such as #my-branch)
|
||||
:preserve
|
||||
git fetch "#{h default_url_to_repo(@merge_request.source_project)}" "#{h @merge_request.source_branch}"
|
||||
git checkout -b "#{h @merge_request.source_project_path}-#{h @merge_request.source_branch}" FETCH_HEAD
|
||||
- else
|
||||
:preserve
|
||||
git fetch origin
|
||||
git checkout -b "#{h @merge_request.source_branch}" "origin/#{h @merge_request.source_branch}"
|
||||
%p
|
||||
%strong Step 2.
|
||||
Review the changes locally
|
||||
|
||||
%p
|
||||
%strong Step 3.
|
||||
Merge the branch and fix any conflicts that come up
|
||||
= clipboard_button(target: "pre#merge-info-3", title: _("Copy commands"))
|
||||
%pre.dark#merge-info-3
|
||||
- if @merge_request.for_fork?
|
||||
:preserve
|
||||
git fetch origin
|
||||
git checkout "#{h @merge_request.target_branch}"
|
||||
git merge --no-ff "#{h @merge_request.source_project_path}-#{h @merge_request.source_branch}"
|
||||
- else
|
||||
:preserve
|
||||
git fetch origin
|
||||
git checkout "#{h @merge_request.target_branch}"
|
||||
git merge --no-ff "#{h @merge_request.source_branch}"
|
||||
%p
|
||||
%strong Step 4.
|
||||
Push the result of the merge to GitLab
|
||||
= clipboard_button(target: "pre#merge-info-4", title: _("Copy commands"))
|
||||
%pre.dark#merge-info-4
|
||||
:preserve
|
||||
git push origin "#{h @merge_request.target_branch}"
|
||||
- unless @merge_request.can_be_merged_by?(current_user)
|
||||
%p
|
||||
Note that pushing to GitLab requires write access to this repository.
|
||||
%p
|
||||
%strong Tip:
|
||||
= succeed '.' do
|
||||
You can also checkout merge requests locally by
|
||||
= link_to 'following these guidelines', help_page_path('user/project/merge_requests/reviewing_and_managing_merge_requests.md', anchor: "checkout-merge-requests-locally-through-the-head-ref"), target: '_blank', rel: 'noopener noreferrer'
|
|
@ -16,9 +16,6 @@
|
|||
.merge-request{ data: { mr_action: mr_action, url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project), lock_version: @merge_request.lock_version } }
|
||||
= render "projects/merge_requests/mr_title"
|
||||
|
||||
- if @merge_request.source_branch_exists?
|
||||
= render "projects/merge_requests/how_to_merge"
|
||||
|
||||
.merge-request-details.issuable-details{ data: { id: @merge_request.project.id } }
|
||||
= render "projects/merge_requests/mr_box"
|
||||
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
|
||||
|
|
|
@ -88,3 +88,6 @@
|
|||
= form.hidden_field :issue_type
|
||||
|
||||
= form.hidden_field :lock_version
|
||||
|
||||
- if @vulnerability_id
|
||||
= hidden_field_tag 'vulnerability_id', @vulnerability_id
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
%h4.gl-flex-grow-1
|
||||
= Feature.enabled?(:security_auto_fix) && @user.bot? ? s_('UserProfile|Bot activity') : s_('UserProfile|Activity')
|
||||
= link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all"
|
||||
.overview-content-list{ data: { href: user_path } }
|
||||
.overview-content-list{ data: { href: user_activity_path } }
|
||||
.center.light.loading
|
||||
.spinner.spinner-md
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
- if can?(current_user, :read_cross_project)
|
||||
%h4.prepend-top-20
|
||||
= s_('UserProfile|Most Recent Activity')
|
||||
.content_list{ data: { href: user_path } }
|
||||
.content_list{ data: { href: user_activity_path } }
|
||||
.loading
|
||||
.spinner.spinner-md
|
||||
- unless @user.bot?
|
||||
|
|
|
@ -1852,7 +1852,7 @@
|
|||
:urgency: :high
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent:
|
||||
:idempotent: true
|
||||
:tags: []
|
||||
- :name: project_daily_statistics
|
||||
:feature_category: :source_code_management
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Worker for updating any project specific caches.
|
||||
class ProjectCacheWorker # rubocop:disable Scalability/IdempotentWorker
|
||||
class ProjectCacheWorker
|
||||
include ApplicationWorker
|
||||
|
||||
LEASE_TIMEOUT = 15.minutes.to_i
|
||||
|
@ -9,6 +9,7 @@ class ProjectCacheWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
feature_category :source_code_management
|
||||
urgency :high
|
||||
loggable_arguments 1, 2, 3
|
||||
idempotent!
|
||||
|
||||
# project_id - The ID of the project for which to flush the cache.
|
||||
# files - An Array containing extra types of files to refresh such as
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add support for filtering direct group members by 2FA enabled/disabled
|
||||
merge_request: 48084
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace how to merge HAML with Vue component
|
||||
merge_request: 48766
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace fa-chevron-down icons with GitLab SVG in gcp cluster form
|
||||
merge_request: 48656
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move users#show.json to users#activity.json
|
||||
merge_request: 48712
|
||||
author: Takuya Noguchi
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add analytics_devops_adoption_snapshots table
|
||||
merge_request: 47388
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add loading state to assignees header
|
||||
merge_request: 48392
|
||||
author:
|
||||
type: added
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: Add loading state to assignees dropdown
|
||||
title: Add loading state to boards assignees header dropdown
|
||||
merge_request: 47848
|
||||
author:
|
||||
type: added
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
- dependency_proxy
|
||||
- dependency_scanning
|
||||
- design_management
|
||||
- design_system
|
||||
- devops_reports
|
||||
- disaster_recovery
|
||||
- dynamic_application_security_testing
|
||||
|
@ -54,7 +53,6 @@
|
|||
- git_lfs
|
||||
- gitaly
|
||||
- gitlab_docs
|
||||
- gitlab_handbook
|
||||
- global_search
|
||||
- helm_chart_registry
|
||||
- importers
|
||||
|
@ -69,7 +67,6 @@
|
|||
- issue_tracking
|
||||
- jenkins_importer
|
||||
- jira_importer
|
||||
- jupyter_notebooks
|
||||
- kubernetes_management
|
||||
- license_compliance
|
||||
- live_preview
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
concern :gitactionable do
|
||||
scope(path: '*repository_path', format: false) do
|
||||
constraints(repository_path: Gitlab::PathRegex.repository_git_route_regex) do
|
||||
scope(module: :repositories) do
|
||||
# Git HTTP API
|
||||
scope(controller: :git_http) do
|
||||
get '/info/refs', action: :info_refs
|
||||
post '/git-upload-pack', action: :git_upload_pack
|
||||
post '/git-receive-pack', action: :git_receive_pack
|
||||
end
|
||||
end
|
||||
|
||||
concern :lfsable do
|
||||
# NOTE: LFS routes are exposed on all repository types, but we still check for
|
||||
# LFS availability on the repository container in LfsRequest#require_lfs_enabled!
|
||||
|
||||
# Git LFS API (metadata)
|
||||
scope(path: 'info/lfs/objects', controller: :lfs_api) do
|
||||
post :batch
|
||||
|
@ -25,36 +29,19 @@ concern :lfsable do
|
|||
scope(path: 'gitlab-lfs/objects/*oid', controller: :lfs_storage, constraints: { oid: /[a-f0-9]{64}/ }) do
|
||||
get '/', action: :download
|
||||
|
||||
scope constraints: { size: /[0-9]+/ } do
|
||||
constraints(size: /[0-9]+/) do
|
||||
put '/*size/authorize', action: :upload_authorize
|
||||
put '/*size', action: :upload_finalize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Git route for personal and project snippets
|
||||
scope(path: ':namespace_id/:repository_id',
|
||||
format: nil,
|
||||
constraints: { namespace_id: Gitlab::PathRegex.personal_and_project_snippets_path_regex, repository_id: /\d+\.git/ },
|
||||
module: :repositories) do
|
||||
concerns :gitactionable
|
||||
end
|
||||
|
||||
scope(path: '*namespace_id/:repository_id',
|
||||
format: nil,
|
||||
constraints: { namespace_id: Gitlab::PathRegex.full_namespace_route_regex }) do
|
||||
scope(constraints: { repository_id: Gitlab::PathRegex.project_git_route_regex }) do
|
||||
scope(module: :repositories) do
|
||||
concerns :gitactionable
|
||||
concerns :lfsable
|
||||
end
|
||||
end
|
||||
|
||||
# Redirect /group/project.wiki.git to the project wiki
|
||||
scope(format: true, constraints: { repository_id: Gitlab::PathRegex.project_wiki_git_route_regex, format: :git }) do
|
||||
constraints(repository_path: Gitlab::PathRegex.repository_wiki_git_route_regex) do
|
||||
wiki_redirect = redirect do |params, request|
|
||||
project_id = params[:repository_id].delete_suffix('.wiki')
|
||||
path = [params[:namespace_id], project_id, 'wikis'].join('/')
|
||||
container_path = params[:repository_path].delete_suffix('.wiki.git')
|
||||
path = File.join(container_path, '-', 'wikis')
|
||||
path << "?#{request.query_string}" unless request.query_string.blank?
|
||||
path
|
||||
end
|
||||
|
@ -63,22 +50,14 @@ scope(path: '*namespace_id/:repository_id',
|
|||
end
|
||||
|
||||
# Redirect /group/project/info/refs to /group/project.git/info/refs
|
||||
scope(constraints: { repository_id: Gitlab::PathRegex.project_route_regex }) do
|
||||
# Allow /info/refs, /info/refs?service=git-upload-pack, and
|
||||
# /info/refs?service=git-receive-pack, but nothing else.
|
||||
#
|
||||
git_http_handshake = lambda do |request|
|
||||
::Constraints::ProjectUrlConstrainer.new.matches?(request, existence_check: false) &&
|
||||
(request.query_string.blank? ||
|
||||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
|
||||
end
|
||||
|
||||
# This allows cloning a repository without the trailing `.git`
|
||||
constraints(repository_path: Gitlab::PathRegex.repository_route_regex) do
|
||||
ref_redirect = redirect do |params, request|
|
||||
path = "#{params[:namespace_id]}/#{params[:repository_id]}.git/info/refs"
|
||||
path = "#{params[:repository_path]}.git/info/refs"
|
||||
path << "?#{request.query_string}" unless request.query_string.blank?
|
||||
path
|
||||
end
|
||||
|
||||
get '/info/refs', constraints: git_http_handshake, to: ref_redirect
|
||||
get '/info/refs', constraints: ::Constraints::RepositoryRedirectUrlConstrainer.new, to: ref_redirect
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateAnalyticsDevopsAdoptionSnapshots < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
create_table :analytics_devops_adoption_snapshots do |t|
|
||||
t.references :segment, index: false, null: false, foreign_key: { to_table: :analytics_devops_adoption_segments, on_delete: :cascade }
|
||||
t.datetime_with_timezone :recorded_at, null: false
|
||||
t.boolean :issue_opened, null: false
|
||||
t.boolean :merge_request_opened, null: false
|
||||
t.boolean :merge_request_approved, null: false
|
||||
t.boolean :runner_configured, null: false
|
||||
t.boolean :pipeline_succeeded, null: false
|
||||
t.boolean :deploy_succeeded, null: false
|
||||
t.boolean :security_scan_succeeded, null: false
|
||||
|
||||
t.index [:segment_id, :recorded_at], name: 'index_on_snapshots_segment_id_recorded_at'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
7a905f8e636be21e328a622d9871018903982989836e6e0def09fd2c2826691f
|
|
@ -8990,6 +8990,28 @@ CREATE SEQUENCE analytics_devops_adoption_segments_id_seq
|
|||
|
||||
ALTER SEQUENCE analytics_devops_adoption_segments_id_seq OWNED BY analytics_devops_adoption_segments.id;
|
||||
|
||||
CREATE TABLE analytics_devops_adoption_snapshots (
|
||||
id bigint NOT NULL,
|
||||
segment_id bigint NOT NULL,
|
||||
recorded_at timestamp with time zone NOT NULL,
|
||||
issue_opened boolean NOT NULL,
|
||||
merge_request_opened boolean NOT NULL,
|
||||
merge_request_approved boolean NOT NULL,
|
||||
runner_configured boolean NOT NULL,
|
||||
pipeline_succeeded boolean NOT NULL,
|
||||
deploy_succeeded boolean NOT NULL,
|
||||
security_scan_succeeded boolean NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE analytics_devops_adoption_snapshots_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE analytics_devops_adoption_snapshots_id_seq OWNED BY analytics_devops_adoption_snapshots.id;
|
||||
|
||||
CREATE TABLE analytics_instance_statistics_measurements (
|
||||
id bigint NOT NULL,
|
||||
count bigint NOT NULL,
|
||||
|
@ -17773,6 +17795,8 @@ ALTER TABLE ONLY analytics_devops_adoption_segment_selections ALTER COLUMN id SE
|
|||
|
||||
ALTER TABLE ONLY analytics_devops_adoption_segments ALTER COLUMN id SET DEFAULT nextval('analytics_devops_adoption_segments_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY analytics_devops_adoption_snapshots ALTER COLUMN id SET DEFAULT nextval('analytics_devops_adoption_snapshots_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY analytics_instance_statistics_measurements ALTER COLUMN id SET DEFAULT nextval('analytics_instance_statistics_measurements_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY appearances ALTER COLUMN id SET DEFAULT nextval('appearances_id_seq'::regclass);
|
||||
|
@ -18754,6 +18778,9 @@ ALTER TABLE ONLY analytics_devops_adoption_segment_selections
|
|||
ALTER TABLE ONLY analytics_devops_adoption_segments
|
||||
ADD CONSTRAINT analytics_devops_adoption_segments_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY analytics_devops_adoption_snapshots
|
||||
ADD CONSTRAINT analytics_devops_adoption_snapshots_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY analytics_instance_statistics_measurements
|
||||
ADD CONSTRAINT analytics_instance_statistics_measurements_pkey PRIMARY KEY (id);
|
||||
|
||||
|
@ -21632,6 +21659,8 @@ CREATE UNIQUE INDEX index_on_segment_selections_project_id_segment_id ON analyti
|
|||
|
||||
CREATE INDEX index_on_segment_selections_segment_id ON analytics_devops_adoption_segment_selections USING btree (segment_id);
|
||||
|
||||
CREATE INDEX index_on_snapshots_segment_id_recorded_at ON analytics_devops_adoption_snapshots USING btree (segment_id, recorded_at);
|
||||
|
||||
CREATE INDEX index_on_users_lower_email ON users USING btree (lower((email)::text));
|
||||
|
||||
CREATE INDEX index_on_users_lower_username ON users USING btree (lower((username)::text));
|
||||
|
@ -23742,6 +23771,9 @@ ALTER TABLE ONLY saml_group_links
|
|||
ALTER TABLE ONLY group_custom_attributes
|
||||
ADD CONSTRAINT fk_rails_246e0db83a FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY analytics_devops_adoption_snapshots
|
||||
ADD CONSTRAINT fk_rails_25da9a92c0 FOREIGN KEY (segment_id) REFERENCES analytics_devops_adoption_segments(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY cluster_agents
|
||||
ADD CONSTRAINT fk_rails_25e9fc2d5d FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -5298,12 +5298,12 @@ Represents a DAST Site Validation
|
|||
"""
|
||||
type DastSiteValidation {
|
||||
"""
|
||||
ID of the site validation
|
||||
Global ID of the site validation
|
||||
"""
|
||||
id: DastSiteValidationID!
|
||||
|
||||
"""
|
||||
The status of the validation
|
||||
Status of the site validation
|
||||
"""
|
||||
status: DastSiteProfileValidationStatusEnum!
|
||||
}
|
||||
|
@ -6539,33 +6539,18 @@ type DevopsAdoptionSegment {
|
|||
"""
|
||||
Assigned groups
|
||||
"""
|
||||
groups(
|
||||
"""
|
||||
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
|
||||
): GroupConnection
|
||||
groups: [Group!]
|
||||
|
||||
"""
|
||||
ID of the segment
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
The latest adoption metrics for the segment
|
||||
"""
|
||||
latestSnapshot: DevopsAdoptionSnapshot
|
||||
|
||||
"""
|
||||
Name of the segment
|
||||
"""
|
||||
|
@ -6607,6 +6592,61 @@ type DevopsAdoptionSegmentEdge {
|
|||
node: DevopsAdoptionSegment
|
||||
}
|
||||
|
||||
"""
|
||||
Snapshot
|
||||
"""
|
||||
type DevopsAdoptionSnapshot {
|
||||
"""
|
||||
At least one deployment succeeded
|
||||
"""
|
||||
deploySucceeded: Boolean!
|
||||
|
||||
"""
|
||||
The end time for the snapshot where the data points were collected
|
||||
"""
|
||||
endTime: Time!
|
||||
|
||||
"""
|
||||
At least one issue was opened
|
||||
"""
|
||||
issueOpened: Boolean!
|
||||
|
||||
"""
|
||||
At least one merge request was approved
|
||||
"""
|
||||
mergeRequestApproved: Boolean!
|
||||
|
||||
"""
|
||||
At least one merge request was opened
|
||||
"""
|
||||
mergeRequestOpened: Boolean!
|
||||
|
||||
"""
|
||||
At least one pipeline succeeded
|
||||
"""
|
||||
pipelineSucceeded: Boolean!
|
||||
|
||||
"""
|
||||
The time the snapshot was recorded
|
||||
"""
|
||||
recordedAt: Time!
|
||||
|
||||
"""
|
||||
At least one runner was used
|
||||
"""
|
||||
runnerConfigured: Boolean!
|
||||
|
||||
"""
|
||||
At least one security scan succeeded
|
||||
"""
|
||||
securityScanSucceeded: Boolean!
|
||||
|
||||
"""
|
||||
The start time for the snapshot where the data points were collected
|
||||
"""
|
||||
startTime: Time!
|
||||
}
|
||||
|
||||
input DiffImagePositionInput {
|
||||
"""
|
||||
Merge base of the branch the comment was made on
|
||||
|
@ -7803,12 +7843,12 @@ type EpicIssue implements CurrentUserTodos & Noteable {
|
|||
author: User!
|
||||
|
||||
"""
|
||||
Indicates the issue is blocked
|
||||
Indicates the issue is blocked.
|
||||
"""
|
||||
blocked: Boolean!
|
||||
|
||||
"""
|
||||
Count of issues blocking this issue
|
||||
Count of issues blocking this issue.
|
||||
"""
|
||||
blockedByCount: Int
|
||||
|
||||
|
@ -7918,7 +7958,7 @@ type EpicIssue implements CurrentUserTodos & Noteable {
|
|||
emailsDisabled: Boolean!
|
||||
|
||||
"""
|
||||
Epic to which this issue belongs
|
||||
Epic to which this issue belongs.
|
||||
"""
|
||||
epic: Epic
|
||||
|
||||
|
@ -7953,7 +7993,7 @@ type EpicIssue implements CurrentUserTodos & Noteable {
|
|||
iid: ID!
|
||||
|
||||
"""
|
||||
Iteration of the issue
|
||||
Iteration of the issue.
|
||||
"""
|
||||
iteration: Iteration
|
||||
|
||||
|
@ -8088,7 +8128,7 @@ type EpicIssue implements CurrentUserTodos & Noteable {
|
|||
state: IssueState!
|
||||
|
||||
"""
|
||||
Indicates whether an issue is published to the status page
|
||||
Indicates whether an issue is published to the status page.
|
||||
"""
|
||||
statusPagePublishedIncident: Boolean
|
||||
|
||||
|
@ -8168,7 +8208,7 @@ type EpicIssue implements CurrentUserTodos & Noteable {
|
|||
webUrl: String!
|
||||
|
||||
"""
|
||||
Weight of the issue
|
||||
Weight of the issue.
|
||||
"""
|
||||
weight: Int
|
||||
}
|
||||
|
@ -9872,41 +9912,6 @@ type Group {
|
|||
webUrl: String!
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for Group.
|
||||
"""
|
||||
type GroupConnection {
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [GroupEdge]
|
||||
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [Group]
|
||||
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type GroupEdge {
|
||||
"""
|
||||
A cursor for use in pagination.
|
||||
"""
|
||||
cursor: String!
|
||||
|
||||
"""
|
||||
The item at the end of the edge.
|
||||
"""
|
||||
node: Group
|
||||
}
|
||||
|
||||
"""
|
||||
Identifier of Group
|
||||
"""
|
||||
|
@ -10484,12 +10489,12 @@ type Issue implements CurrentUserTodos & Noteable {
|
|||
author: User!
|
||||
|
||||
"""
|
||||
Indicates the issue is blocked
|
||||
Indicates the issue is blocked.
|
||||
"""
|
||||
blocked: Boolean!
|
||||
|
||||
"""
|
||||
Count of issues blocking this issue
|
||||
Count of issues blocking this issue.
|
||||
"""
|
||||
blockedByCount: Int
|
||||
|
||||
|
@ -10599,7 +10604,7 @@ type Issue implements CurrentUserTodos & Noteable {
|
|||
emailsDisabled: Boolean!
|
||||
|
||||
"""
|
||||
Epic to which this issue belongs
|
||||
Epic to which this issue belongs.
|
||||
"""
|
||||
epic: Epic
|
||||
|
||||
|
@ -10629,7 +10634,7 @@ type Issue implements CurrentUserTodos & Noteable {
|
|||
iid: ID!
|
||||
|
||||
"""
|
||||
Iteration of the issue
|
||||
Iteration of the issue.
|
||||
"""
|
||||
iteration: Iteration
|
||||
|
||||
|
@ -10759,7 +10764,7 @@ type Issue implements CurrentUserTodos & Noteable {
|
|||
state: IssueState!
|
||||
|
||||
"""
|
||||
Indicates whether an issue is published to the status page
|
||||
Indicates whether an issue is published to the status page.
|
||||
"""
|
||||
statusPagePublishedIncident: Boolean
|
||||
|
||||
|
@ -10839,7 +10844,7 @@ type Issue implements CurrentUserTodos & Noteable {
|
|||
webUrl: String!
|
||||
|
||||
"""
|
||||
Weight of the issue
|
||||
Weight of the issue.
|
||||
"""
|
||||
weight: Int
|
||||
}
|
||||
|
|
|
@ -14507,7 +14507,7 @@
|
|||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of the site validation",
|
||||
"description": "Global ID of the site validation",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -14525,7 +14525,7 @@
|
|||
},
|
||||
{
|
||||
"name": "status",
|
||||
"description": "The status of the validation",
|
||||
"description": "Status of the site validation",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -18042,51 +18042,20 @@
|
|||
"name": "groups",
|
||||
"description": "Assigned groups",
|
||||
"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
|
||||
}
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GroupConnection",
|
||||
"name": "Group",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
|
@ -18109,6 +18078,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "latestSnapshot",
|
||||
"description": "The latest adoption metrics for the segment",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DevopsAdoptionSnapshot",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the segment",
|
||||
|
@ -18247,6 +18230,199 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DevopsAdoptionSnapshot",
|
||||
"description": "Snapshot",
|
||||
"fields": [
|
||||
{
|
||||
"name": "deploySucceeded",
|
||||
"description": "At least one deployment succeeded",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "endTime",
|
||||
"description": "The end time for the snapshot where the data points were collected",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "issueOpened",
|
||||
"description": "At least one issue was opened",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequestApproved",
|
||||
"description": "At least one merge request was approved",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "mergeRequestOpened",
|
||||
"description": "At least one merge request was opened",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pipelineSucceeded",
|
||||
"description": "At least one pipeline succeeded",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "recordedAt",
|
||||
"description": "The time the snapshot was recorded",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "runnerConfigured",
|
||||
"description": "At least one runner was used",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "securityScanSucceeded",
|
||||
"description": "At least one security scan succeeded",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "startTime",
|
||||
"description": "The start time for the snapshot where the data points were collected",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DiffImagePositionInput",
|
||||
|
@ -21660,7 +21836,7 @@
|
|||
},
|
||||
{
|
||||
"name": "blocked",
|
||||
"description": "Indicates the issue is blocked",
|
||||
"description": "Indicates the issue is blocked.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -21678,7 +21854,7 @@
|
|||
},
|
||||
{
|
||||
"name": "blockedByCount",
|
||||
"description": "Count of issues blocking this issue",
|
||||
"description": "Count of issues blocking this issue.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -21976,7 +22152,7 @@
|
|||
},
|
||||
{
|
||||
"name": "epic",
|
||||
"description": "Epic to which this issue belongs",
|
||||
"description": "Epic to which this issue belongs.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -22082,7 +22258,7 @@
|
|||
},
|
||||
{
|
||||
"name": "iteration",
|
||||
"description": "Iteration of the issue",
|
||||
"description": "Iteration of the issue.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -22424,7 +22600,7 @@
|
|||
},
|
||||
{
|
||||
"name": "statusPagePublishedIncident",
|
||||
"description": "Indicates whether an issue is published to the status page",
|
||||
"description": "Indicates whether an issue is published to the status page.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -22696,7 +22872,7 @@
|
|||
},
|
||||
{
|
||||
"name": "weight",
|
||||
"description": "Weight of the issue",
|
||||
"description": "Weight of the issue.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -26953,118 +27129,6 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "GroupConnection",
|
||||
"description": "The connection type for Group.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "edges",
|
||||
"description": "A list of edges.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "GroupEdge",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "nodes",
|
||||
"description": "A list of nodes.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Group",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pageInfo",
|
||||
"description": "Information to aid in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PageInfo",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "GroupEdge",
|
||||
"description": "An edge in a connection.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "cursor",
|
||||
"description": "A cursor for use in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"description": "The item at the end of the edge.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Group",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "GroupID",
|
||||
|
@ -28744,7 +28808,7 @@
|
|||
},
|
||||
{
|
||||
"name": "blocked",
|
||||
"description": "Indicates the issue is blocked",
|
||||
"description": "Indicates the issue is blocked.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -28762,7 +28826,7 @@
|
|||
},
|
||||
{
|
||||
"name": "blockedByCount",
|
||||
"description": "Count of issues blocking this issue",
|
||||
"description": "Count of issues blocking this issue.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -29060,7 +29124,7 @@
|
|||
},
|
||||
{
|
||||
"name": "epic",
|
||||
"description": "Epic to which this issue belongs",
|
||||
"description": "Epic to which this issue belongs.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -29152,7 +29216,7 @@
|
|||
},
|
||||
{
|
||||
"name": "iteration",
|
||||
"description": "Iteration of the issue",
|
||||
"description": "Iteration of the issue.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -29480,7 +29544,7 @@
|
|||
},
|
||||
{
|
||||
"name": "statusPagePublishedIncident",
|
||||
"description": "Indicates whether an issue is published to the status page",
|
||||
"description": "Indicates whether an issue is published to the status page.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
@ -29752,7 +29816,7 @@
|
|||
},
|
||||
{
|
||||
"name": "weight",
|
||||
"description": "Weight of the issue",
|
||||
"description": "Weight of the issue.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
|
|
|
@ -887,8 +887,8 @@ Represents a DAST Site Validation.
|
|||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `id` | DastSiteValidationID! | ID of the site validation |
|
||||
| `status` | DastSiteProfileValidationStatusEnum! | The status of the validation |
|
||||
| `id` | DastSiteValidationID! | Global ID of the site validation |
|
||||
| `status` | DastSiteProfileValidationStatusEnum! | Status of the site validation |
|
||||
|
||||
### DastSiteValidationCreatePayload
|
||||
|
||||
|
@ -1104,10 +1104,28 @@ Segment.
|
|||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `groups` | GroupConnection | Assigned groups |
|
||||
| `groups` | Group! => Array | Assigned groups |
|
||||
| `id` | ID! | ID of the segment |
|
||||
| `latestSnapshot` | DevopsAdoptionSnapshot | The latest adoption metrics for the segment |
|
||||
| `name` | String! | Name of the segment |
|
||||
|
||||
### DevopsAdoptionSnapshot
|
||||
|
||||
Snapshot.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `deploySucceeded` | Boolean! | At least one deployment succeeded |
|
||||
| `endTime` | Time! | The end time for the snapshot where the data points were collected |
|
||||
| `issueOpened` | Boolean! | At least one issue was opened |
|
||||
| `mergeRequestApproved` | Boolean! | At least one merge request was approved |
|
||||
| `mergeRequestOpened` | Boolean! | At least one merge request was opened |
|
||||
| `pipelineSucceeded` | Boolean! | At least one pipeline succeeded |
|
||||
| `recordedAt` | Time! | The time the snapshot was recorded |
|
||||
| `runnerConfigured` | Boolean! | At least one runner was used |
|
||||
| `securityScanSucceeded` | Boolean! | At least one security scan succeeded |
|
||||
| `startTime` | Time! | The start time for the snapshot where the data points were collected |
|
||||
|
||||
### DiffPosition
|
||||
|
||||
| Field | Type | Description |
|
||||
|
@ -1309,8 +1327,8 @@ Relationship between an epic and an issue.
|
|||
| `alertManagementAlert` | AlertManagementAlert | Alert associated to this issue |
|
||||
| `assignees` | UserConnection | Assignees of the issue |
|
||||
| `author` | User! | User that created the issue |
|
||||
| `blocked` | Boolean! | Indicates the issue is blocked |
|
||||
| `blockedByCount` | Int | Count of issues blocking this issue |
|
||||
| `blocked` | Boolean! | Indicates the issue is blocked. |
|
||||
| `blockedByCount` | Int | Count of issues blocking this issue. |
|
||||
| `closedAt` | Time | Timestamp of when the issue was closed |
|
||||
| `confidential` | Boolean! | Indicates the issue is confidential |
|
||||
| `createdAt` | Time! | Timestamp of when the issue was created |
|
||||
|
@ -1323,14 +1341,14 @@ Relationship between an epic and an issue.
|
|||
| `downvotes` | Int! | Number of downvotes the issue has received |
|
||||
| `dueDate` | Time | Due date of the issue |
|
||||
| `emailsDisabled` | Boolean! | Indicates if a project has email notifications disabled: `true` if email notifications are disabled |
|
||||
| `epic` | Epic | Epic to which this issue belongs |
|
||||
| `epic` | Epic | Epic to which this issue belongs. |
|
||||
| `epicIssueId` | ID! | ID of the epic-issue relation |
|
||||
| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
|
||||
| `humanTimeEstimate` | String | Human-readable time estimate of the issue |
|
||||
| `humanTotalTimeSpent` | String | Human-readable total time reported as spent on the issue |
|
||||
| `id` | ID | Global ID of the epic-issue relation |
|
||||
| `iid` | ID! | Internal ID of the issue |
|
||||
| `iteration` | Iteration | Iteration of the issue |
|
||||
| `iteration` | Iteration | Iteration of the issue. |
|
||||
| `labels` | LabelConnection | Labels of the issue |
|
||||
| `metricImages` | MetricImage! => Array | Metric images associated to the issue. |
|
||||
| `milestone` | Milestone | Milestone of the issue |
|
||||
|
@ -1344,7 +1362,7 @@ Relationship between an epic and an issue.
|
|||
| `severity` | IssuableSeverity | Severity level of the incident |
|
||||
| `slaDueAt` | Time | Timestamp of when the issue SLA expires. |
|
||||
| `state` | IssueState! | State of the issue |
|
||||
| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page |
|
||||
| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page. |
|
||||
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the issue |
|
||||
| `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue |
|
||||
| `timeEstimate` | Int! | Time estimate of the issue |
|
||||
|
@ -1360,7 +1378,7 @@ Relationship between an epic and an issue.
|
|||
| `userPermissions` | IssuePermissions! | Permissions for the current user on the resource |
|
||||
| `webPath` | String! | Web path of the issue |
|
||||
| `webUrl` | String! | Web URL of the issue |
|
||||
| `weight` | Int | Weight of the issue |
|
||||
| `weight` | Int | Weight of the issue. |
|
||||
|
||||
### EpicPermissions
|
||||
|
||||
|
@ -1608,8 +1626,8 @@ Represents a recorded measurement (object count) for the Admins.
|
|||
| `alertManagementAlert` | AlertManagementAlert | Alert associated to this issue |
|
||||
| `assignees` | UserConnection | Assignees of the issue |
|
||||
| `author` | User! | User that created the issue |
|
||||
| `blocked` | Boolean! | Indicates the issue is blocked |
|
||||
| `blockedByCount` | Int | Count of issues blocking this issue |
|
||||
| `blocked` | Boolean! | Indicates the issue is blocked. |
|
||||
| `blockedByCount` | Int | Count of issues blocking this issue. |
|
||||
| `closedAt` | Time | Timestamp of when the issue was closed |
|
||||
| `confidential` | Boolean! | Indicates the issue is confidential |
|
||||
| `createdAt` | Time! | Timestamp of when the issue was created |
|
||||
|
@ -1622,13 +1640,13 @@ Represents a recorded measurement (object count) for the Admins.
|
|||
| `downvotes` | Int! | Number of downvotes the issue has received |
|
||||
| `dueDate` | Time | Due date of the issue |
|
||||
| `emailsDisabled` | Boolean! | Indicates if a project has email notifications disabled: `true` if email notifications are disabled |
|
||||
| `epic` | Epic | Epic to which this issue belongs |
|
||||
| `epic` | Epic | Epic to which this issue belongs. |
|
||||
| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
|
||||
| `humanTimeEstimate` | String | Human-readable time estimate of the issue |
|
||||
| `humanTotalTimeSpent` | String | Human-readable total time reported as spent on the issue |
|
||||
| `id` | ID! | ID of the issue |
|
||||
| `iid` | ID! | Internal ID of the issue |
|
||||
| `iteration` | Iteration | Iteration of the issue |
|
||||
| `iteration` | Iteration | Iteration of the issue. |
|
||||
| `labels` | LabelConnection | Labels of the issue |
|
||||
| `metricImages` | MetricImage! => Array | Metric images associated to the issue. |
|
||||
| `milestone` | Milestone | Milestone of the issue |
|
||||
|
@ -1641,7 +1659,7 @@ Represents a recorded measurement (object count) for the Admins.
|
|||
| `severity` | IssuableSeverity | Severity level of the incident |
|
||||
| `slaDueAt` | Time | Timestamp of when the issue SLA expires. |
|
||||
| `state` | IssueState! | State of the issue |
|
||||
| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page |
|
||||
| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page. |
|
||||
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the issue |
|
||||
| `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue |
|
||||
| `timeEstimate` | Int! | Time estimate of the issue |
|
||||
|
@ -1657,7 +1675,7 @@ Represents a recorded measurement (object count) for the Admins.
|
|||
| `userPermissions` | IssuePermissions! | Permissions for the current user on the resource |
|
||||
| `webPath` | String! | Web path of the issue |
|
||||
| `webUrl` | String! | Web URL of the issue |
|
||||
| `weight` | Int | Weight of the issue |
|
||||
| `weight` | Int | Weight of the issue. |
|
||||
|
||||
### IssueMoveListPayload
|
||||
|
||||
|
|
|
@ -91,12 +91,22 @@ There are two ways to define the URL to be scanned by DAST:
|
|||
1. Set the `DAST_WEBSITE` [variable](../../../ci/yaml/README.md#variables).
|
||||
|
||||
1. Add it in an `environment_url.txt` file at the root of your project.
|
||||
This is great for testing in dynamic environments. In order to run DAST against
|
||||
an app dynamically created during a GitLab CI/CD pipeline, have the app
|
||||
persist its domain in an `environment_url.txt` file, and DAST
|
||||
automatically parses that file to find its scan target.
|
||||
You can see an [example](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml)
|
||||
of this in our Auto DevOps CI YAML.
|
||||
This is useful for testing in dynamic environments. To run DAST against an application
|
||||
dynamically created during a GitLab CI/CD pipeline, a job that runs prior to the DAST scan must
|
||||
persist the application's domain in an `environment_url.txt` file. DAST automatically parses the
|
||||
`environment_url.txt` file to find its scan target.
|
||||
|
||||
For example, in a job that runs prior to DAST, you could include code that looks similar to:
|
||||
|
||||
```yaml
|
||||
script:
|
||||
- echo http://${CI_PROJECT_ID}-${CI_ENVIRONMENT_SLUG}.domain.com > environment_url.txt
|
||||
artifacts:
|
||||
paths: [environment_url.txt]
|
||||
when: always
|
||||
```
|
||||
|
||||
You can see an example of this in our [Auto DevOps CI YAML](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml) file.
|
||||
|
||||
If both values are set, the `DAST_WEBSITE` value takes precedence.
|
||||
|
||||
|
|
|
@ -31,8 +31,7 @@ module API
|
|||
def access_checker_for(actor, protocol)
|
||||
access_checker_klass.new(actor.key_or_user, container, protocol,
|
||||
authentication_abilities: ssh_authentication_abilities,
|
||||
namespace_path: namespace_path,
|
||||
repository_path: project_path,
|
||||
repository_path: repository_path,
|
||||
redirected_path: redirected_path)
|
||||
end
|
||||
|
||||
|
@ -71,18 +70,22 @@ module API
|
|||
false
|
||||
end
|
||||
|
||||
def project_path
|
||||
project&.path || project_path_match[:project_path]
|
||||
end
|
||||
|
||||
def namespace_path
|
||||
project&.namespace&.full_path || project_path_match[:namespace_path]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project_path_match
|
||||
@project_path_match ||= params[:project].match(Gitlab::PathRegex.full_project_git_path_regex) || {}
|
||||
def repository_path
|
||||
if container
|
||||
"#{container.full_path}.git"
|
||||
elsif params[:project]
|
||||
# When the project doesn't exist, we still need to pass on the path
|
||||
# to support auto-creation in `GitAccessProject`.
|
||||
#
|
||||
# For consistency with the Git HTTP controllers, we normalize the path
|
||||
# to remove a leading slash and ensure a trailing `.git`.
|
||||
#
|
||||
# NOTE: For GitLab Shell, `params[:project]` is the full repository path
|
||||
# from the SSH command, with an optional trailing `.git`.
|
||||
"#{params[:project].delete_prefix('/').delete_suffix('.git')}.git"
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
|
@ -96,7 +99,7 @@ module API
|
|||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
# Project id to pass between components that don't share/don't have
|
||||
# Repository id to pass between components that don't share/don't have
|
||||
# access to the same filesystem mounts
|
||||
def gl_repository
|
||||
repo_type.identifier_for_container(container)
|
||||
|
@ -106,8 +109,9 @@ module API
|
|||
repository.full_path
|
||||
end
|
||||
|
||||
# Return the repository depending on whether we want the wiki or the
|
||||
# regular repository
|
||||
# Return the repository for the detected type and container
|
||||
#
|
||||
# @returns [Repository]
|
||||
def repository
|
||||
@repository ||= repo_type.repository_for(container)
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ module Constraints
|
|||
class ProjectUrlConstrainer
|
||||
def matches?(request, existence_check: true)
|
||||
namespace_path = request.params[:namespace_id]
|
||||
project_path = request.params[:project_id] || request.params[:id] || request.params[:repository_id]
|
||||
project_path = request.params[:project_id] || request.params[:id]
|
||||
full_path = [namespace_path, project_path].join('/')
|
||||
|
||||
return false unless ProjectPathValidator.valid_path?(full_path)
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Constraints
|
||||
class RepositoryRedirectUrlConstrainer
|
||||
def matches?(request)
|
||||
path = request.params[:repository_path].delete_suffix('.git')
|
||||
query = request.query_string
|
||||
|
||||
git_request?(query) && container_path?(path)
|
||||
end
|
||||
|
||||
# Allow /info/refs, /info/refs?service=git-upload-pack, and
|
||||
# /info/refs?service=git-receive-pack, but nothing else.
|
||||
def git_request?(query)
|
||||
query.blank? ||
|
||||
query == 'service=git-upload-pack' ||
|
||||
query == 'service=git-receive-pack'
|
||||
end
|
||||
|
||||
# Check if the path matches any known repository containers.
|
||||
# These also cover wikis, since a `.wiki` suffix is valid in project/group paths too.
|
||||
def container_path?(path)
|
||||
NamespacePathValidator.valid_path?(path) ||
|
||||
ProjectPathValidator.valid_path?(path) ||
|
||||
path =~ Gitlab::PathRegex.full_snippets_repository_path_regex
|
||||
end
|
||||
end
|
||||
end
|
|
@ -30,7 +30,7 @@ module Gitlab
|
|||
Labkit::Context.current.to_h.include?(Labkit::Context.log_key(attribute_name))
|
||||
end
|
||||
|
||||
def initialize(args)
|
||||
def initialize(**args)
|
||||
unknown_attributes = args.keys - APPLICATION_ATTRIBUTES.map(&:name)
|
||||
raise ArgumentError, "#{unknown_attributes} are not known keys" if unknown_attributes.any?
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
stages:
|
||||
- build
|
||||
- test
|
||||
- deploy
|
||||
- dast
|
||||
|
||||
variables:
|
||||
DAST_VERSION: 1
|
||||
# Setting this variable will affect all Security templates
|
||||
# (SAST, Dependency Scanning, ...)
|
||||
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
|
||||
|
||||
dast:
|
||||
stage: dast
|
||||
image:
|
||||
name: "$SECURE_ANALYZERS_PREFIX/dast:$DAST_VERSION"
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
allow_failure: true
|
||||
script:
|
||||
- /analyze
|
||||
artifacts:
|
||||
reports:
|
||||
dast: gl-dast-report.json
|
|
@ -43,7 +43,7 @@ module Gitlab
|
|||
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
|
||||
|
||||
attr_reader :actor, :protocol, :authentication_abilities,
|
||||
:namespace_path, :redirected_path, :auth_result_type,
|
||||
:repository_path, :redirected_path, :auth_result_type,
|
||||
:cmd, :changes
|
||||
attr_accessor :container
|
||||
|
||||
|
@ -57,21 +57,16 @@ module Gitlab
|
|||
raise ArgumentError, "No error message defined for #{key}"
|
||||
end
|
||||
|
||||
def initialize(actor, container, protocol, authentication_abilities:, namespace_path: nil, repository_path: nil, redirected_path: nil, auth_result_type: nil)
|
||||
def initialize(actor, container, protocol, authentication_abilities:, repository_path: nil, redirected_path: nil, auth_result_type: nil)
|
||||
@actor = actor
|
||||
@container = container
|
||||
@protocol = protocol
|
||||
@authentication_abilities = Array(authentication_abilities)
|
||||
@namespace_path = namespace_path
|
||||
@repository_path = repository_path
|
||||
@redirected_path = redirected_path
|
||||
@auth_result_type = auth_result_type
|
||||
end
|
||||
|
||||
def repository_path
|
||||
@repository_path ||= project&.path
|
||||
end
|
||||
|
||||
def check(cmd, changes)
|
||||
@changes = changes
|
||||
@cmd = cmd
|
||||
|
|
|
@ -35,7 +35,19 @@ module Gitlab
|
|||
end
|
||||
|
||||
def namespace
|
||||
@namespace ||= Namespace.find_by_full_path(namespace_path)
|
||||
strong_memoize(:namespace) { Namespace.find_by_full_path(namespace_path) }
|
||||
end
|
||||
|
||||
def namespace_path
|
||||
strong_memoize(:namespace_path) { repository_path_match[:namespace_path] }
|
||||
end
|
||||
|
||||
def project_path
|
||||
strong_memoize(:project_path) { repository_path_match[:project_path] }
|
||||
end
|
||||
|
||||
def repository_path_match
|
||||
strong_memoize(:repository_path_match) { repository_path.match(Gitlab::PathRegex.full_project_git_path_regex) || {} }
|
||||
end
|
||||
|
||||
def ensure_project_on_push!
|
||||
|
@ -44,7 +56,7 @@ module Gitlab
|
|||
return unless user&.can?(:create_projects, namespace)
|
||||
|
||||
project_params = {
|
||||
path: repository_path,
|
||||
path: project_path,
|
||||
namespace_id: namespace.id,
|
||||
visibility_level: Gitlab::VisibilityLevel::PRIVATE
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ module Gitlab
|
|||
field :user_permissions, permission_type,
|
||||
description: description,
|
||||
null: false,
|
||||
resolve: -> (obj, _, _) { obj }
|
||||
method: :itself
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module Gitlab
|
|||
class ProjectCreator
|
||||
attr_reader :repo, :name, :namespace, :current_user, :session_data, :type
|
||||
|
||||
def initialize(repo, name, namespace, current_user, session_data, type: 'github')
|
||||
def initialize(repo, name, namespace, current_user, type: 'github', **session_data)
|
||||
@repo = repo
|
||||
@name = name
|
||||
@namespace = namespace
|
||||
|
|
|
@ -180,12 +180,16 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def project_git_route_regex
|
||||
@project_git_route_regex ||= /#{project_route_regex}\.git/.freeze
|
||||
def repository_route_regex
|
||||
@repository_route_regex ||= /#{full_namespace_route_regex}|#{personal_snippet_repository_path_regex}/.freeze
|
||||
end
|
||||
|
||||
def project_wiki_git_route_regex
|
||||
@project_wiki_git_route_regex ||= /#{PATH_REGEX_STR}\.wiki/.freeze
|
||||
def repository_git_route_regex
|
||||
@repository_git_route_regex ||= /#{repository_route_regex}\.git/.freeze
|
||||
end
|
||||
|
||||
def repository_wiki_git_route_regex
|
||||
@repository_wiki_git_route_regex ||= /#{full_namespace_route_regex}\.wiki\.git/.freeze
|
||||
end
|
||||
|
||||
def full_namespace_path_regex
|
||||
|
@ -250,10 +254,6 @@ module Gitlab
|
|||
%r{\A(#{personal_snippet_repository_path_regex}|#{project_snippet_repository_path_regex})\z}
|
||||
end
|
||||
|
||||
def personal_and_project_snippets_path_regex
|
||||
%r{#{personal_snippet_path_regex}|#{project_snippet_path_regex}}
|
||||
end
|
||||
|
||||
def container_image_regex
|
||||
@container_image_regex ||= %r{([\w\.-]+\/){0,1}[\w\.-]+}.freeze
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module Gitlab
|
|||
NotFoundError = Class.new(StandardError)
|
||||
|
||||
def self.parse(path)
|
||||
repo_path = path.sub(/\.git\z/, '').sub(%r{\A/}, '')
|
||||
repo_path = path.delete_prefix('/').delete_suffix('.git')
|
||||
redirected_path = nil
|
||||
|
||||
# Detect the repo type based on the path, the first one tried is the project
|
||||
|
|
|
@ -81,7 +81,10 @@ namespace :gitlab do
|
|||
|
||||
if head_assets_md5 != master_assets_md5 || !public_assets_webpack_dir_exists
|
||||
FileUtils.rm_r(Tasks::Gitlab::Assets::PUBLIC_ASSETS_WEBPACK_DIR) if public_assets_webpack_dir_exists
|
||||
system('yarn webpack')
|
||||
|
||||
unless system('yarn webpack')
|
||||
abort 'Error: Unable to compile webpack production bundle.'.color(:red)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -9525,6 +9525,9 @@ msgstr ""
|
|||
msgid "DevopsAdoptionSegment|The maximum number of segments has been reached"
|
||||
msgstr ""
|
||||
|
||||
msgid "DevopsAdoptionSegment|The maximum number of selections has been reached"
|
||||
msgstr ""
|
||||
|
||||
msgid "DevopsAdoption|%{selectedCount} group selected (20 max)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -16491,6 +16494,9 @@ msgstr ""
|
|||
msgid "ManualOrdering|Couldn't save the order of the issues"
|
||||
msgstr ""
|
||||
|
||||
msgid "Manually link this issue by adding it to the linked issue section of the %{originating_vulnerability}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Map a FogBugz account ID to a GitLab user"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27815,9 +27821,6 @@ msgstr ""
|
|||
msgid "This issue is currently blocked by the following issues:"
|
||||
msgstr ""
|
||||
|
||||
msgid "This issue is currently blocked by the following issues: %{issues}."
|
||||
msgstr ""
|
||||
|
||||
msgid "This issue is in a child epic of the filtered epic"
|
||||
msgstr ""
|
||||
|
||||
|
@ -28977,6 +28980,9 @@ msgstr ""
|
|||
msgid "Unable to convert Kubernetes logs encoding to UTF-8"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to create link to vulnerability"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unable to fetch unscanned projects"
|
||||
msgstr ""
|
||||
|
||||
|
@ -32815,6 +32821,9 @@ msgstr ""
|
|||
msgid "or"
|
||||
msgstr ""
|
||||
|
||||
msgid "originating vulnerability"
|
||||
msgstr ""
|
||||
|
||||
msgid "out of %d total test"
|
||||
msgid_plural "out of %d total tests"
|
||||
msgstr[0] ""
|
||||
|
|
|
@ -367,8 +367,8 @@ RSpec.describe OmniauthCallbacksController, type: :controller do
|
|||
|
||||
before do
|
||||
stub_last_request_id(last_request_id)
|
||||
stub_omniauth_saml_config({ enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
|
||||
providers: [saml_config] })
|
||||
stub_omniauth_saml_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'],
|
||||
providers: [saml_config])
|
||||
mock_auth_hash_with_saml_xml('saml', +'my-uid', user.email, mock_saml_response)
|
||||
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||
request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth']
|
||||
|
|
|
@ -1128,12 +1128,12 @@ RSpec.describe Projects::IssuesController do
|
|||
{ merge_request_to_resolve_discussions_of: merge_request.iid }
|
||||
end
|
||||
|
||||
def post_issue(issue_params, other_params: {})
|
||||
def post_issue(other_params: {}, **issue_params)
|
||||
post :create, params: { namespace_id: project.namespace.to_param, project_id: project, issue: issue_params, merge_request_to_resolve_discussions_of: merge_request.iid }.merge(other_params)
|
||||
end
|
||||
|
||||
it 'creates an issue for the project' do
|
||||
expect { post_issue({ title: 'Hello' }) }.to change { project.issues.reload.size }.by(1)
|
||||
expect { post_issue(title: 'Hello') }.to change { project.issues.reload.size }.by(1)
|
||||
end
|
||||
|
||||
it "doesn't overwrite given params" do
|
||||
|
@ -1157,7 +1157,7 @@ RSpec.describe Projects::IssuesController do
|
|||
|
||||
describe "resolving a single discussion" do
|
||||
before do
|
||||
post_issue({ title: 'Hello' }, other_params: { discussion_to_resolve: discussion.id })
|
||||
post_issue(title: 'Hello', other_params: { discussion_to_resolve: discussion.id })
|
||||
end
|
||||
it 'resolves a single discussion' do
|
||||
discussion.first_note.reload
|
||||
|
|
|
@ -9,16 +9,9 @@ RSpec.describe Repositories::GitHttpController do
|
|||
let_it_be(:personal_snippet) { create(:personal_snippet, :public, :repository) }
|
||||
let_it_be(:project_snippet) { create(:project_snippet, :public, :repository, project: project) }
|
||||
|
||||
let(:namespace_id) { project.namespace.to_param }
|
||||
let(:repository_id) { project.path + '.git' }
|
||||
let(:container_params) do
|
||||
{
|
||||
namespace_id: namespace_id,
|
||||
repository_id: repository_id
|
||||
}
|
||||
end
|
||||
|
||||
let(:params) { container_params }
|
||||
shared_examples Repositories::GitHttpController do
|
||||
let(:repository_path) { "#{container.full_path}.git" }
|
||||
let(:params) { { repository_path: repository_path } }
|
||||
|
||||
describe 'HEAD #info_refs' do
|
||||
it 'returns 403' do
|
||||
|
@ -28,9 +21,8 @@ RSpec.describe Repositories::GitHttpController do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'info_refs behavior' do
|
||||
describe 'GET #info_refs' do
|
||||
let(:params) { container_params.merge(service: 'git-upload-pack') }
|
||||
let(:params) { super().merge(service: 'git-upload-pack') }
|
||||
|
||||
it 'returns 401 for unauthenticated requests to public repositories when http protocol is disabled' do
|
||||
stub_application_setting(enabled_git_access_protocol: 'ssh')
|
||||
|
@ -43,6 +35,26 @@ RSpec.describe Repositories::GitHttpController do
|
|||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'calls the right access checker class with the right object' do
|
||||
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
|
||||
|
||||
access_double = double
|
||||
options = {
|
||||
authentication_abilities: [:download_code],
|
||||
repository_path: repository_path,
|
||||
redirected_path: nil,
|
||||
auth_result_type: :none
|
||||
}
|
||||
|
||||
expect(access_checker_class).to receive(:new)
|
||||
.with(nil, container, 'http', hash_including(options))
|
||||
.and_return(access_double)
|
||||
|
||||
allow(access_double).to receive(:check).and_return(false)
|
||||
|
||||
get :info_refs, params: params
|
||||
end
|
||||
|
||||
context 'with authorized user' do
|
||||
before do
|
||||
request.headers.merge! auth_env(user.username, user.password, nil)
|
||||
|
@ -97,14 +109,29 @@ RSpec.describe Repositories::GitHttpController do
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'git_upload_pack behavior' do |expected|
|
||||
describe 'POST #git_upload_pack' do
|
||||
before do
|
||||
allow(controller).to receive(:authenticate_user).and_return(true)
|
||||
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
|
||||
allow(controller).to receive(:access_check).and_return(nil)
|
||||
end
|
||||
|
||||
it 'returns 200' do
|
||||
post :git_upload_pack, params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository container is a project' do
|
||||
it_behaves_like Repositories::GitHttpController do
|
||||
let(:container) { project }
|
||||
let(:user) { project.owner }
|
||||
let(:access_checker_class) { Gitlab::GitAccess }
|
||||
|
||||
describe 'POST #git_upload_pack' do
|
||||
before do
|
||||
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
|
||||
end
|
||||
|
||||
def send_request
|
||||
|
@ -123,99 +150,48 @@ RSpec.describe Repositories::GitHttpController do
|
|||
end
|
||||
end
|
||||
|
||||
if expected
|
||||
context 'when project_statistics_sync feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(project_statistics_sync: false)
|
||||
end
|
||||
|
||||
it 'updates project statistics async' do
|
||||
it 'updates project statistics async for projects' do
|
||||
expect(ProjectDailyStatisticsWorker).to receive(:perform_async)
|
||||
|
||||
send_request
|
||||
end
|
||||
end
|
||||
|
||||
it 'updates project statistics sync' do
|
||||
it 'updates project statistics sync for projects' do
|
||||
expect { send_request }.to change {
|
||||
Projects::DailyStatisticsFinder.new(project).total_fetch_count
|
||||
Projects::DailyStatisticsFinder.new(container).total_fetch_count
|
||||
}.from(0).to(1)
|
||||
end
|
||||
else
|
||||
context 'when project_statistics_sync feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(project_statistics_sync: false)
|
||||
end
|
||||
|
||||
it 'does not update project statistics' do
|
||||
expect(ProjectDailyStatisticsWorker).not_to receive(:perform_async)
|
||||
|
||||
send_request
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not update project statistics' do
|
||||
expect { send_request }.not_to change {
|
||||
Projects::DailyStatisticsFinder.new(project).total_fetch_count
|
||||
}.from(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'access checker class' do
|
||||
let(:params) { container_params.merge(service: 'git-upload-pack') }
|
||||
|
||||
it 'calls the right access class checker with the right object' do
|
||||
allow(controller).to receive(:verify_workhorse_api!).and_return(true)
|
||||
|
||||
access_double = double
|
||||
expect(expected_class).to receive(:new).with(anything, expected_object, 'http', anything).and_return(access_double)
|
||||
allow(access_double).to receive(:check).and_return(false)
|
||||
|
||||
get :info_refs, params: params
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository container is a project' do
|
||||
it_behaves_like 'info_refs behavior' do
|
||||
context 'when repository container is a project wiki' do
|
||||
it_behaves_like Repositories::GitHttpController do
|
||||
let(:container) { create(:project_wiki, :empty_repo, project: project) }
|
||||
let(:user) { project.owner }
|
||||
end
|
||||
|
||||
it_behaves_like 'git_upload_pack behavior', true
|
||||
it_behaves_like 'access checker class' do
|
||||
let(:expected_class) { Gitlab::GitAccess }
|
||||
let(:expected_object) { project }
|
||||
let(:access_checker_class) { Gitlab::GitAccessWiki }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository container is a personal snippet' do
|
||||
let(:namespace_id) { 'snippets' }
|
||||
let(:repository_id) { personal_snippet.to_param + '.git' }
|
||||
|
||||
it_behaves_like 'info_refs behavior' do
|
||||
it_behaves_like Repositories::GitHttpController do
|
||||
let(:container) { personal_snippet }
|
||||
let(:user) { personal_snippet.author }
|
||||
end
|
||||
|
||||
it_behaves_like 'git_upload_pack behavior', false
|
||||
it_behaves_like 'access checker class' do
|
||||
let(:expected_class) { Gitlab::GitAccessSnippet }
|
||||
let(:expected_object) { personal_snippet }
|
||||
let(:access_checker_class) { Gitlab::GitAccessSnippet }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when repository container is a project snippet' do
|
||||
let(:namespace_id) { project.full_path + '/snippets' }
|
||||
let(:repository_id) { project_snippet.to_param + '.git' }
|
||||
|
||||
it_behaves_like 'info_refs behavior' do
|
||||
it_behaves_like Repositories::GitHttpController do
|
||||
let(:container) { project_snippet }
|
||||
let(:user) { project_snippet.author }
|
||||
end
|
||||
|
||||
it_behaves_like 'git_upload_pack behavior', false
|
||||
it_behaves_like 'access checker class' do
|
||||
let(:expected_class) { Gitlab::GitAccessSnippet }
|
||||
let(:expected_object) { project_snippet }
|
||||
let(:access_checker_class) { Gitlab::GitAccessSnippet }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -23,8 +23,7 @@ RSpec.describe Repositories::LfsStorageController do
|
|||
|
||||
let(:params) do
|
||||
{
|
||||
namespace_id: project.namespace.path,
|
||||
repository_id: "#{project.path}.git",
|
||||
repository_path: "#{project.full_path}.git",
|
||||
oid: '6b9765d3888aaec789e8c309eb05b05c3a87895d6ad70d2264bd7270fff665ac',
|
||||
size: '6725030'
|
||||
}
|
||||
|
|
|
@ -114,6 +114,113 @@ RSpec.describe UsersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET #activity' do
|
||||
context 'with rendered views' do
|
||||
render_views
|
||||
|
||||
describe 'when logged in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'renders the show template' do
|
||||
get :show, params: { username: user.username }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(response).to render_template('show')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when logged out' do
|
||||
it 'renders the show template' do
|
||||
get :activity, params: { username: user.username }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template('show')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when public visibility level is restricted' do
|
||||
before do
|
||||
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
|
||||
end
|
||||
|
||||
context 'when logged out' do
|
||||
it 'redirects to login page' do
|
||||
get :activity, params: { username: user.username }
|
||||
expect(response).to redirect_to new_user_session_path
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'renders show' do
|
||||
get :activity, params: { username: user.username }
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template('show')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user by that username does not exist' do
|
||||
context 'when logged out' do
|
||||
it 'redirects to login page' do
|
||||
get :activity, params: { username: 'nonexistent' }
|
||||
expect(response).to redirect_to new_user_session_path
|
||||
end
|
||||
end
|
||||
|
||||
context 'when logged in' do
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'renders 404' do
|
||||
get :activity, params: { username: 'nonexistent' }
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'json with events' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
Gitlab::DataBuilder::Push.build_sample(project, user)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'loads events' do
|
||||
get :activity, params: { username: user }, format: :json
|
||||
|
||||
expect(assigns(:events)).not_to be_empty
|
||||
end
|
||||
|
||||
it 'hides events if the user cannot read cross project' do
|
||||
allow(Ability).to receive(:allowed?).and_call_original
|
||||
expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
|
||||
|
||||
get :activity, params: { username: user }, format: :json
|
||||
|
||||
expect(assigns(:events)).to be_empty
|
||||
end
|
||||
|
||||
it 'hides events if the user has a private profile' do
|
||||
Gitlab::DataBuilder::Push.build_sample(project, private_user)
|
||||
|
||||
get :activity, params: { username: private_user.username }, format: :json
|
||||
|
||||
expect(assigns(:events)).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #calendar' do
|
||||
context 'for user' do
|
||||
let(:project) { create(:project) }
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :devops_adoption_segment_selection, class: 'Analytics::DevopsAdoption::SegmentSelection' do
|
||||
association :segment, factory: :devops_adoption_segment
|
||||
project
|
||||
|
||||
trait :project do
|
||||
group { nil }
|
||||
project
|
||||
end
|
||||
|
||||
trait :group do
|
||||
project { nil }
|
||||
group
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :devops_adoption_segment, class: 'Analytics::DevopsAdoption::Segment' do
|
||||
sequence(:name) { |n| "Segment #{n}" }
|
||||
end
|
||||
end
|
|
@ -13,5 +13,6 @@ FactoryBot.define do
|
|||
sequence(:past_time) { |n| 4.hours.ago + (2 * n).seconds }
|
||||
sequence(:iid)
|
||||
sequence(:sha) { |n| Digest::SHA1.hexdigest("commit-like-#{n}") }
|
||||
sequence(:oid) { |n| Digest::SHA2.hexdigest("oid-like-#{n}") }
|
||||
sequence(:variable) { |n| "var#{n}" }
|
||||
end
|
||||
|
|
|
@ -129,4 +129,48 @@ RSpec.describe GroupMembersFinder, '#execute' do
|
|||
expect(result.to_a).not_to include(member_with_2fa)
|
||||
expect(result.to_a).to match_array([member1, member2])
|
||||
end
|
||||
|
||||
it 'returns direct members with two-factor auth if requested by owner' do
|
||||
group.add_owner(user1)
|
||||
group.add_maintainer(user2)
|
||||
nested_group.add_maintainer(user3)
|
||||
member_with_2fa = nested_group.add_maintainer(user5)
|
||||
|
||||
result = described_class.new(nested_group, user1, params: { two_factor: 'enabled' }).execute(include_relations: [:direct])
|
||||
|
||||
expect(result.to_a).to match_array([member_with_2fa])
|
||||
end
|
||||
|
||||
it 'returns inherited members with two-factor auth if requested by owner' do
|
||||
group.add_owner(user1)
|
||||
member_with_2fa = group.add_maintainer(user5)
|
||||
nested_group.add_maintainer(user2)
|
||||
nested_group.add_maintainer(user3)
|
||||
|
||||
result = described_class.new(nested_group, user1, params: { two_factor: 'enabled' }).execute(include_relations: [:inherited])
|
||||
|
||||
expect(result.to_a).to match_array([member_with_2fa])
|
||||
end
|
||||
|
||||
it 'returns direct members without two-factor auth if requested by owner' do
|
||||
group.add_owner(user1)
|
||||
group.add_maintainer(user2)
|
||||
member3 = nested_group.add_maintainer(user3)
|
||||
nested_group.add_maintainer(user5)
|
||||
|
||||
result = described_class.new(nested_group, user1, params: { two_factor: 'disabled' }).execute(include_relations: [:direct])
|
||||
|
||||
expect(result.to_a).to match_array([member3])
|
||||
end
|
||||
|
||||
it 'returns inherited members without two-factor auth if requested by owner' do
|
||||
member1 = group.add_owner(user1)
|
||||
group.add_maintainer(user5)
|
||||
nested_group.add_maintainer(user2)
|
||||
nested_group.add_maintainer(user3)
|
||||
|
||||
result = described_class.new(nested_group, user1, params: { two_factor: 'disabled' }).execute(include_relations: [:inherited])
|
||||
|
||||
expect(result.to_a).to match_array([member1])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ describe('BoardCardAssigneeDropdown', () => {
|
|||
data() {
|
||||
return {
|
||||
search,
|
||||
selected: store.getters.activeIssue.assignees,
|
||||
selected: [],
|
||||
participants,
|
||||
};
|
||||
},
|
||||
|
@ -63,14 +63,13 @@ describe('BoardCardAssigneeDropdown', () => {
|
|||
[getIssueParticipants, getIssueParticipantsSpy],
|
||||
[searchUsers, getSearchUsersSpy],
|
||||
]);
|
||||
|
||||
wrapper = mount(BoardAssigneeDropdown, {
|
||||
localVue,
|
||||
apolloProvider: fakeApollo,
|
||||
data() {
|
||||
return {
|
||||
search,
|
||||
selected: store.getters.activeIssue.assignees,
|
||||
selected: [],
|
||||
participants,
|
||||
};
|
||||
},
|
||||
|
@ -369,4 +368,18 @@ describe('BoardCardAssigneeDropdown', () => {
|
|||
expect(findByText(currentUser.username).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setting an assignee', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('passes loading state from Vuex to BoardEditableItem', async () => {
|
||||
store.state.isSettingAssignees = true;
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.find(BoardEditableItem).props('loading')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -690,10 +690,18 @@ describe('setAssignees', () => {
|
|||
{},
|
||||
{ activeIssue: { iid, referencePath: refPath }, commit: () => {} },
|
||||
[
|
||||
{
|
||||
type: 'SET_ASSIGNEE_LOADING',
|
||||
payload: true,
|
||||
},
|
||||
{
|
||||
type: 'UPDATE_ISSUE_BY_ID',
|
||||
payload: { prop: 'assignees', issueId: undefined, value: [node] },
|
||||
},
|
||||
{
|
||||
type: 'SET_ASSIGNEE_LOADING',
|
||||
payload: false,
|
||||
},
|
||||
],
|
||||
[],
|
||||
done,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue