Merge branch 'master' into bootstrap4
This commit is contained in:
commit
b8401cd0b2
395 changed files with 4980 additions and 3624 deletions
|
@ -364,10 +364,11 @@ update-tests-metadata:
|
|||
- rspec_flaky/
|
||||
policy: push
|
||||
script:
|
||||
- retry gem install fog-aws mime-types
|
||||
- retry gem install fog-aws mime-types activesupport
|
||||
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
|
||||
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
|
||||
- scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
|
||||
- FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
|
||||
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
|
||||
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
|
||||
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
|
||||
|
@ -735,16 +736,50 @@ codequality:
|
|||
expire_in: 1 week
|
||||
|
||||
sast:
|
||||
<<: *except-docs
|
||||
image: registry.gitlab.com/gitlab-org/gl-sast:latest
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
image: docker:stable
|
||||
variables:
|
||||
CONFIDENCE_LEVEL: 2
|
||||
SAST_CONFIDENCE_LEVEL: 2
|
||||
DOCKER_DRIVER: overlay2
|
||||
allow_failure: true
|
||||
tags: []
|
||||
before_script: []
|
||||
cache: {}
|
||||
dependencies: []
|
||||
services:
|
||||
- docker:stable-dind
|
||||
script:
|
||||
- /app/bin/run .
|
||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||
- docker run
|
||||
--env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}"
|
||||
--volume "$PWD:/code"
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock
|
||||
"registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
|
||||
artifacts:
|
||||
paths: [gl-sast-report.json]
|
||||
|
||||
dependency_scanning:
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
image: docker:stable
|
||||
variables:
|
||||
DOCKER_DRIVER: overlay2
|
||||
allow_failure: true
|
||||
tags: []
|
||||
before_script: []
|
||||
cache: {}
|
||||
dependencies: []
|
||||
services:
|
||||
- docker:stable-dind
|
||||
script:
|
||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||
- docker run
|
||||
--env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}"
|
||||
--volume "$PWD:/code"
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock
|
||||
"registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
|
||||
artifacts:
|
||||
paths: [gl-dependency-scanning-report.json]
|
||||
|
||||
qa:internal:
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
services: []
|
||||
|
|
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -2,6 +2,24 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 10.6.4 (2018-04-09)
|
||||
|
||||
### Fixed (8 changes, 1 of them is from the community)
|
||||
|
||||
- Correct copy text for the promote milestone and label modals. !17726
|
||||
- Avoid validation errors when running the Pages domain verification service. !17992
|
||||
- Fix autolinking URLs containing ampersands. !18045
|
||||
- Fix exceptions raised when migrating pipeline stages in the background. !18076
|
||||
- Work around Prometheus Helm chart name changes to fix integration. !18206 (joshlambert)
|
||||
- Don't show Jump to Discussion button on Issues.
|
||||
- Fix listing commit branch/tags that contain special characters.
|
||||
- Fix 404 in group boards when moving issue between lists.
|
||||
|
||||
### Performance (1 change)
|
||||
|
||||
- Free open file descriptors and libgit2 buffers in UpdatePagesService.
|
||||
|
||||
|
||||
## 10.6.3 (2018-04-03)
|
||||
|
||||
### Security (2 changes)
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.94.0
|
||||
0.95.0
|
||||
|
|
|
@ -1 +1 @@
|
|||
4.0.0
|
||||
4.1.0
|
||||
|
|
|
@ -321,6 +321,9 @@ GEM
|
|||
rubyntlm (~> 0.5)
|
||||
globalid (0.4.1)
|
||||
activesupport (>= 4.2.0)
|
||||
goldiloader (2.0.1)
|
||||
activerecord (>= 4.2, < 5.2)
|
||||
activesupport (>= 4.2, < 5.2)
|
||||
gollum-grit_adapter (1.0.1)
|
||||
gitlab-grit (~> 2.7, >= 2.7.1)
|
||||
gollum-lib (4.2.7)
|
||||
|
@ -878,7 +881,7 @@ GEM
|
|||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
slack-notifier (1.5.1)
|
||||
spinach (0.10.1)
|
||||
spinach (0.8.10)
|
||||
colorize
|
||||
gherkin-ruby (>= 0.3.2)
|
||||
json
|
||||
|
@ -1072,6 +1075,7 @@ DEPENDENCIES
|
|||
gitlab-markup (~> 1.6.2)
|
||||
gitlab-styles (~> 2.3)
|
||||
gitlab_omniauth-ldap (~> 2.0.4)
|
||||
goldiloader (~> 2.0)
|
||||
gollum-lib (~> 4.2)
|
||||
gollum-rugged_adapter (~> 0.4.4)
|
||||
gon (~> 6.1.0)
|
||||
|
|
|
@ -55,22 +55,20 @@
|
|||
},
|
||||
methods: {
|
||||
successCallback(resp) {
|
||||
return resp.json().then((response) => {
|
||||
// depending of the endpoint the response can either bring a `pipelines` key or not.
|
||||
const pipelines = response.pipelines || response;
|
||||
this.setCommonData(pipelines);
|
||||
// depending of the endpoint the response can either bring a `pipelines` key or not.
|
||||
const pipelines = resp.data.pipelines || resp.data;
|
||||
this.setCommonData(pipelines);
|
||||
|
||||
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
|
||||
detail: {
|
||||
pipelines: response,
|
||||
},
|
||||
});
|
||||
|
||||
// notifiy to update the count in tabs
|
||||
if (this.$el.parentElement) {
|
||||
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
|
||||
}
|
||||
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
|
||||
detail: {
|
||||
pipelines: resp.data,
|
||||
},
|
||||
});
|
||||
|
||||
// notifiy to update the count in tabs
|
||||
if (this.$el.parentElement) {
|
||||
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -22,7 +22,7 @@ export default {
|
|||
...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']),
|
||||
...mapGetters(['currentMergeRequest']),
|
||||
shouldHideEditor() {
|
||||
return this.file && this.file.binary && !this.file.raw;
|
||||
return this.file && this.file.binary && !this.file.content;
|
||||
},
|
||||
editTabCSS() {
|
||||
return {
|
||||
|
@ -212,7 +212,7 @@ export default {
|
|||
<content-viewer
|
||||
v-if="shouldHideEditor || file.viewMode === 'preview'"
|
||||
:content="file.content || file.raw"
|
||||
:path="file.rawPath"
|
||||
:path="file.rawPath || file.path"
|
||||
:file-size="file.size"
|
||||
:project-path="file.projectId"/>
|
||||
</div>
|
||||
|
|
|
@ -1190,12 +1190,12 @@ export default class Notes {
|
|||
addForm = false;
|
||||
let lineTypeSelector = '';
|
||||
rowCssToAdd =
|
||||
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content discussion-notes"></div></td></tr>';
|
||||
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
|
||||
// In parallel view, look inside the correct left/right pane
|
||||
if (this.isParallelView()) {
|
||||
lineTypeSelector = `.${lineType}`;
|
||||
rowCssToAdd =
|
||||
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content discussion-notes"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content discussion-notes"></div></td></tr>';
|
||||
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
|
||||
}
|
||||
const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
|
||||
let notesContent = targetRow.find(notesContentSelector);
|
||||
|
|
|
@ -317,10 +317,10 @@ Please check your network connection and try again.`;
|
|||
<note-signed-out-widget v-if="!isLoggedIn" />
|
||||
<discussion-locked-widget
|
||||
issuable-type="issue"
|
||||
v-else-if="!canCreateNote"
|
||||
v-else-if="isLocked(getNoteableData) && !canCreateNote"
|
||||
/>
|
||||
<ul
|
||||
v-else
|
||||
v-else-if="canCreateNote"
|
||||
class="notes notes-form timeline">
|
||||
<li class="timeline-entry">
|
||||
<div class="timeline-entry-inner">
|
||||
|
|
|
@ -40,6 +40,10 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canAwardEmoji: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
canDelete: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
@ -74,9 +78,6 @@ export default {
|
|||
shouldShowActionsDropdown() {
|
||||
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
|
||||
},
|
||||
canAddAwardEmoji() {
|
||||
return this.currentUserId;
|
||||
},
|
||||
isAuthoredByCurrentUser() {
|
||||
return this.authorId === this.currentUserId;
|
||||
},
|
||||
|
@ -149,7 +150,7 @@ export default {
|
|||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="canAddAwardEmoji"
|
||||
v-if="canAwardEmoji"
|
||||
class="note-actions-item">
|
||||
<a
|
||||
v-tooltip
|
||||
|
|
|
@ -28,6 +28,10 @@ export default {
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
canAwardEmoji: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getUserData']),
|
||||
|
@ -67,9 +71,6 @@ export default {
|
|||
isAuthoredByMe() {
|
||||
return this.noteAuthorId === this.getUserData.id;
|
||||
},
|
||||
isLoggedIn() {
|
||||
return this.getUserData.id;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.emojiSmiling = emojiSmiling;
|
||||
|
@ -156,7 +157,7 @@ export default {
|
|||
return title;
|
||||
},
|
||||
handleAward(awardName) {
|
||||
if (!this.isLoggedIn) {
|
||||
if (!this.canAwardEmoji) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -208,7 +209,7 @@ export default {
|
|||
</span>
|
||||
</button>
|
||||
<div
|
||||
v-if="isLoggedIn"
|
||||
v-if="canAwardEmoji"
|
||||
class="award-menu-holder">
|
||||
<button
|
||||
v-tooltip
|
||||
|
|
|
@ -112,6 +112,7 @@ export default {
|
|||
:note-author-id="note.author.id"
|
||||
:awards="note.award_emoji"
|
||||
:toggle-award-path="note.toggle_award_path"
|
||||
:can-award-emoji="note.current_user.can_award_emoji"
|
||||
/>
|
||||
<note-attachment
|
||||
v-if="note.attachment"
|
||||
|
|
|
@ -258,7 +258,9 @@ Please check your network connection and try again.`;
|
|||
:key="note.id"
|
||||
/>
|
||||
</ul>
|
||||
<div class="discussion-reply-holder">
|
||||
<div
|
||||
:class="{ 'is-replying': isReplying }"
|
||||
class="discussion-reply-holder">
|
||||
<template v-if="!isReplying && canReply">
|
||||
<div
|
||||
class="btn-group d-flex discussion-with-resolve-btn"
|
||||
|
|
|
@ -177,6 +177,7 @@ export default {
|
|||
:note-id="note.id"
|
||||
:access-level="note.human_access"
|
||||
:can-edit="note.current_user.can_edit"
|
||||
:can-award-emoji="note.current_user.can_award_emoji"
|
||||
:can-delete="note.current_user.can_edit"
|
||||
:can-report-as-abuse="canReportAsAbuse"
|
||||
:report-abuse-path="note.report_abuse_path"
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import initSettingsPanels from '~/settings_panels';
|
||||
import setupProjectEdit from '~/project_edit';
|
||||
import initConfirmDangerModal from '~/confirm_danger_modal';
|
||||
import ProjectNew from '../shared/project_new';
|
||||
import initProjectLoadingSpinner from '../shared/save_project_loader';
|
||||
import projectAvatar from '../shared/project_avatar';
|
||||
import initProjectPermissionsSettings from '../shared/permissions';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ProjectNew(); // eslint-disable-line no-new
|
||||
initProjectLoadingSpinner();
|
||||
setupProjectEdit();
|
||||
// Initialize expandable settings panels
|
||||
initSettingsPanels();
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import ProjectNew from '../shared/project_new';
|
||||
import initProjectLoadingSpinner from '../shared/save_project_loader';
|
||||
import initProjectVisibilitySelector from '../../../project_visibility';
|
||||
import initProjectNew from '../../../projects/project_new';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ProjectNew(); // eslint-disable-line no-new
|
||||
initProjectLoadingSpinner();
|
||||
initProjectVisibilitySelector();
|
||||
initProjectNew.bindEvents();
|
||||
});
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/
|
||||
|
||||
import $ from 'jquery';
|
||||
import VisibilitySelect from '../../../visibility_select';
|
||||
|
||||
function highlightChanges($elm) {
|
||||
$elm.addClass('highlight-changes');
|
||||
setTimeout(() => $elm.removeClass('highlight-changes'), 10);
|
||||
}
|
||||
|
||||
export default class ProjectNew {
|
||||
constructor() {
|
||||
this.toggleSettings = this.toggleSettings.bind(this);
|
||||
this.$selects = $('.features select');
|
||||
this.$repoSelects = this.$selects.filter('.js-repo-select');
|
||||
this.$projectSelects = this.$selects.not('.js-repo-select');
|
||||
|
||||
$('.project-edit-container').on('ajax:before', () => {
|
||||
$('.project-edit-container').hide();
|
||||
return $('.save-project-loader').show();
|
||||
});
|
||||
|
||||
this.initVisibilitySelect();
|
||||
|
||||
this.toggleSettings();
|
||||
this.toggleSettingsOnclick();
|
||||
this.toggleRepoVisibility();
|
||||
}
|
||||
|
||||
initVisibilitySelect() {
|
||||
const visibilityContainer = document.querySelector('.js-visibility-select');
|
||||
if (!visibilityContainer) return;
|
||||
const visibilitySelect = new VisibilitySelect(visibilityContainer);
|
||||
visibilitySelect.init();
|
||||
|
||||
const $visibilitySelect = $(visibilityContainer).find('select');
|
||||
let projectVisibility = $visibilitySelect.val();
|
||||
const PROJECT_VISIBILITY_PRIVATE = '0';
|
||||
|
||||
$visibilitySelect.on('change', () => {
|
||||
const newProjectVisibility = $visibilitySelect.val();
|
||||
|
||||
if (projectVisibility !== newProjectVisibility) {
|
||||
this.$projectSelects.each((idx, select) => {
|
||||
const $select = $(select);
|
||||
const $options = $select.find('option');
|
||||
const values = $.map($options, e => e.value);
|
||||
|
||||
// if switched to "private", limit visibility options
|
||||
if (newProjectVisibility === PROJECT_VISIBILITY_PRIVATE) {
|
||||
if ($select.val() !== values[0] && $select.val() !== values[1]) {
|
||||
$select.val(values[1]).trigger('change');
|
||||
highlightChanges($select);
|
||||
}
|
||||
$options.slice(2).disable();
|
||||
}
|
||||
|
||||
// if switched from "private", increase visibility for non-disabled options
|
||||
if (projectVisibility === PROJECT_VISIBILITY_PRIVATE) {
|
||||
$options.enable();
|
||||
if ($select.val() !== values[0] && $select.val() !== values[values.length - 1]) {
|
||||
$select.val(values[values.length - 1]).trigger('change');
|
||||
highlightChanges($select);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
projectVisibility = newProjectVisibility;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleSettings() {
|
||||
this.$selects.each(function () {
|
||||
var $select = $(this);
|
||||
var className = $select.data('field')
|
||||
.replace(/_/g, '-')
|
||||
.replace('access-level', 'feature');
|
||||
ProjectNew._showOrHide($select, '.' + className);
|
||||
});
|
||||
}
|
||||
|
||||
toggleSettingsOnclick() {
|
||||
this.$selects.on('change', this.toggleSettings);
|
||||
}
|
||||
|
||||
static _showOrHide(checkElement, container) {
|
||||
const $container = $(container);
|
||||
|
||||
if ($(checkElement).val() !== '0') {
|
||||
return $container.show();
|
||||
}
|
||||
return $container.hide();
|
||||
}
|
||||
|
||||
toggleRepoVisibility() {
|
||||
var $repoAccessLevel = $('.js-repo-access-level select');
|
||||
var $lfsEnabledOption = $('.js-lfs-enabled select');
|
||||
var containerRegistry = document.querySelectorAll('.js-container-registry')[0];
|
||||
var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
|
||||
var prevSelectedVal = parseInt($repoAccessLevel.val(), 10);
|
||||
|
||||
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
|
||||
.nextAll()
|
||||
.hide();
|
||||
|
||||
$repoAccessLevel
|
||||
.off('change')
|
||||
.on('change', function () {
|
||||
var selectedVal = parseInt($repoAccessLevel.val(), 10);
|
||||
|
||||
this.$repoSelects.each(function () {
|
||||
var $this = $(this);
|
||||
var repoSelectVal = parseInt($this.val(), 10);
|
||||
|
||||
$this.find('option').enable();
|
||||
|
||||
if (selectedVal < repoSelectVal || repoSelectVal === prevSelectedVal) {
|
||||
$this.val(selectedVal).trigger('change');
|
||||
highlightChanges($this);
|
||||
}
|
||||
|
||||
$this.find("option[value='" + selectedVal + "']").nextAll().disable();
|
||||
});
|
||||
|
||||
if (selectedVal) {
|
||||
this.$repoSelects.removeClass('disabled');
|
||||
|
||||
if ($lfsEnabledOption.length) {
|
||||
$lfsEnabledOption.removeClass('disabled');
|
||||
highlightChanges($lfsEnabledOption);
|
||||
}
|
||||
if (containerRegistry) {
|
||||
containerRegistry.style.display = '';
|
||||
}
|
||||
} else {
|
||||
this.$repoSelects.addClass('disabled');
|
||||
|
||||
if ($lfsEnabledOption.length) {
|
||||
$lfsEnabledOption.val('false').addClass('disabled');
|
||||
highlightChanges($lfsEnabledOption);
|
||||
}
|
||||
if (containerRegistry) {
|
||||
containerRegistry.style.display = 'none';
|
||||
containerRegistryCheckbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
prevSelectedVal = selectedVal;
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
export default function initProjectLoadingSpinner() {
|
||||
const $formContainer = $('.project-edit-container');
|
||||
const $loadingSpinner = $('.save-project-loader');
|
||||
|
||||
// show loading spinner when saving
|
||||
$formContainer.on('ajax:before', () => {
|
||||
$formContainer.hide();
|
||||
$loadingSpinner.show();
|
||||
});
|
||||
}
|
|
@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
|
|||
* Does that setting the current selected tab in the localStorage
|
||||
*/
|
||||
export default class SigninTabsMemoizer {
|
||||
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
|
||||
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.new-session-tabs' } = {}) {
|
||||
this.currentTabKey = currentTabKey;
|
||||
this.tabSelector = tabSelector;
|
||||
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
|
||||
|
|
|
@ -7,10 +7,7 @@
|
|||
import TablePagination from '../../vue_shared/components/table_pagination.vue';
|
||||
import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
|
||||
import NavigationControls from './nav_controls.vue';
|
||||
import {
|
||||
getParameterByName,
|
||||
parseQueryStringIntoObject,
|
||||
} from '../../lib/utils/common_utils';
|
||||
import { getParameterByName } from '../../lib/utils/common_utils';
|
||||
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
|
||||
|
||||
export default {
|
||||
|
@ -19,10 +16,7 @@
|
|||
NavigationTabs,
|
||||
NavigationControls,
|
||||
},
|
||||
mixins: [
|
||||
pipelinesMixin,
|
||||
CIPaginationMixin,
|
||||
],
|
||||
mixins: [pipelinesMixin, CIPaginationMixin],
|
||||
props: {
|
||||
store: {
|
||||
type: Object,
|
||||
|
@ -147,25 +141,26 @@
|
|||
*/
|
||||
shouldRenderTabs() {
|
||||
const { stateMap } = this.$options;
|
||||
return this.hasMadeRequest &&
|
||||
[
|
||||
stateMap.loading,
|
||||
stateMap.tableList,
|
||||
stateMap.error,
|
||||
stateMap.emptyTab,
|
||||
].includes(this.stateToRender);
|
||||
return (
|
||||
this.hasMadeRequest &&
|
||||
[stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
|
||||
this.stateToRender,
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
shouldRenderButtons() {
|
||||
return (this.newPipelinePath ||
|
||||
this.resetCachePath ||
|
||||
this.ciLintPath) && this.shouldRenderTabs;
|
||||
return (
|
||||
(this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
|
||||
);
|
||||
},
|
||||
|
||||
shouldRenderPagination() {
|
||||
return !this.isLoading &&
|
||||
return (
|
||||
!this.isLoading &&
|
||||
this.state.pipelines.length &&
|
||||
this.state.pageInfo.total > this.state.pageInfo.perPage;
|
||||
this.state.pageInfo.total > this.state.pageInfo.perPage
|
||||
);
|
||||
},
|
||||
|
||||
emptyTabMessage() {
|
||||
|
@ -229,15 +224,13 @@
|
|||
},
|
||||
methods: {
|
||||
successCallback(resp) {
|
||||
return resp.json().then((response) => {
|
||||
// Because we are polling & the user is interacting verify if the response received
|
||||
// matches the last request made
|
||||
if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) {
|
||||
this.store.storeCount(response.count);
|
||||
this.store.storePagination(resp.headers);
|
||||
this.setCommonData(response.pipelines);
|
||||
}
|
||||
});
|
||||
// Because we are polling & the user is interacting verify if the response received
|
||||
// matches the last request made
|
||||
if (_.isEqual(resp.config.params, this.requestData)) {
|
||||
this.store.storeCount(resp.data.count);
|
||||
this.store.storePagination(resp.headers);
|
||||
this.setCommonData(resp.data.pipelines);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Handles URL and query parameter changes.
|
||||
|
@ -251,8 +244,9 @@
|
|||
this.updateInternalState(parameters);
|
||||
|
||||
// fetch new data
|
||||
return this.service.getPipelines(this.requestData)
|
||||
.then((response) => {
|
||||
return this.service
|
||||
.getPipelines(this.requestData)
|
||||
.then(response => {
|
||||
this.isLoading = false;
|
||||
this.successCallback(response);
|
||||
|
||||
|
@ -271,13 +265,11 @@
|
|||
handleResetRunnersCache(endpoint) {
|
||||
this.isResetCacheButtonLoading = true;
|
||||
|
||||
this.service.postAction(endpoint)
|
||||
this.service
|
||||
.postAction(endpoint)
|
||||
.then(() => {
|
||||
this.isResetCacheButtonLoading = false;
|
||||
createFlash(
|
||||
s__('Pipelines|Project cache successfully reset.'),
|
||||
'notice',
|
||||
);
|
||||
createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
|
||||
})
|
||||
.catch(() => {
|
||||
this.isResetCacheButtonLoading = false;
|
||||
|
|
|
@ -13,16 +13,16 @@
|
|||
* 3. Merge request widget
|
||||
* 4. Commit widget
|
||||
*/
|
||||
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
import Flash from '../../flash';
|
||||
import icon from '../../vue_shared/components/icon.vue';
|
||||
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import Icon from '../../vue_shared/components/icon.vue';
|
||||
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
loadingIcon,
|
||||
icon,
|
||||
LoadingIcon,
|
||||
Icon,
|
||||
},
|
||||
|
||||
directives: {
|
||||
|
@ -88,9 +88,8 @@
|
|||
},
|
||||
|
||||
fetchJobs() {
|
||||
this.$http.get(this.stage.dropdown_path)
|
||||
.then(response => response.json())
|
||||
.then((data) => {
|
||||
axios.get(this.stage.dropdown_path)
|
||||
.then(({ data }) => {
|
||||
this.dropdownContent = data.html;
|
||||
this.isLoading = false;
|
||||
})
|
||||
|
@ -98,8 +97,7 @@
|
|||
this.closeDropdown();
|
||||
this.isLoading = false;
|
||||
|
||||
const flash = new Flash('Something went wrong on our end.');
|
||||
return flash;
|
||||
Flash('Something went wrong on our end.');
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -40,10 +40,8 @@ export default class pipelinesMediator {
|
|||
}
|
||||
|
||||
successCallback(response) {
|
||||
return response.json().then((data) => {
|
||||
this.state.isLoading = false;
|
||||
this.store.storePipeline(data);
|
||||
});
|
||||
this.state.isLoading = false;
|
||||
this.store.storePipeline(response.data);
|
||||
}
|
||||
|
||||
errorCallback() {
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
|
||||
Vue.use(VueResource);
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
|
||||
export default class PipelineService {
|
||||
constructor(endpoint) {
|
||||
this.pipeline = Vue.resource(endpoint);
|
||||
this.pipeline = endpoint;
|
||||
}
|
||||
|
||||
getPipeline() {
|
||||
return this.pipeline.get();
|
||||
return axios.get(this.pipeline);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
postAction(endpoint) {
|
||||
return Vue.http.post(`${endpoint}.json`);
|
||||
return axios.post(`${endpoint}.json`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,27 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
import '../../vue_shared/vue_resource_interceptor';
|
||||
|
||||
Vue.use(VueResource);
|
||||
import axios from '../../lib/utils/axios_utils';
|
||||
|
||||
export default class PipelinesService {
|
||||
|
||||
/**
|
||||
* Commits and merge request endpoints need to be requested with `.json`.
|
||||
*
|
||||
* The url provided to request the pipelines in the new merge request
|
||||
* page already has `.json`.
|
||||
*
|
||||
* @param {String} root
|
||||
*/
|
||||
* Commits and merge request endpoints need to be requested with `.json`.
|
||||
*
|
||||
* The url provided to request the pipelines in the new merge request
|
||||
* page already has `.json`.
|
||||
*
|
||||
* @param {String} root
|
||||
*/
|
||||
constructor(root) {
|
||||
let endpoint;
|
||||
|
||||
if (root.indexOf('.json') === -1) {
|
||||
endpoint = `${root}.json`;
|
||||
this.endpoint = `${root}.json`;
|
||||
} else {
|
||||
endpoint = root;
|
||||
this.endpoint = root;
|
||||
}
|
||||
|
||||
this.pipelines = Vue.resource(endpoint);
|
||||
}
|
||||
|
||||
getPipelines(data = {}) {
|
||||
const { scope, page } = data;
|
||||
return this.pipelines.get({ scope, page });
|
||||
return axios.get(this.endpoint, {
|
||||
params: { scope, page },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,7 +30,8 @@ export default class PipelinesService {
|
|||
* @param {String} endpoint
|
||||
* @return {Promise}
|
||||
*/
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
postAction(endpoint) {
|
||||
return Vue.http.post(`${endpoint}.json`);
|
||||
return axios.post(`${endpoint}.json`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
export default {
|
||||
name: 'time-tracking-estimate-only-pane',
|
||||
props: {
|
||||
timeEstimateHumanReadable: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="time-tracking-estimate-only-pane">
|
||||
<span class="bold">
|
||||
{{ s__('TimeTracking|Estimated:') }}
|
||||
</span>
|
||||
{{ timeEstimateHumanReadable }}
|
||||
</div>
|
||||
`,
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'TimeTrackingEstimateOnlyPane',
|
||||
props: {
|
||||
timeEstimateHumanReadable: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="time-tracking-estimate-only-pane">
|
||||
<span class="bold">
|
||||
{{ s__('TimeTracking|Estimated:') }}
|
||||
</span>
|
||||
{{ timeEstimateHumanReadable }}
|
||||
</div>
|
||||
</template>
|
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import { sprintf, s__ } from '../../../locale';
|
||||
|
||||
export default {
|
||||
name: 'time-tracking-help-state',
|
||||
name: 'TimeTrackingHelpState',
|
||||
props: {
|
||||
rootPath: {
|
||||
type: String,
|
||||
|
@ -27,26 +28,28 @@ export default {
|
|||
);
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="time-tracking-help-state">
|
||||
<div class="time-tracking-info">
|
||||
<h4>
|
||||
{{ __('Track time with quick actions') }}
|
||||
</h4>
|
||||
<p>
|
||||
{{ __('Quick actions can be used in the issues description and comment boxes.') }}
|
||||
</p>
|
||||
<p v-html="estimateText">
|
||||
</p>
|
||||
<p v-html="spendText">
|
||||
</p>
|
||||
<a
|
||||
class="btn btn-default learn-more-button"
|
||||
:href="href"
|
||||
>
|
||||
{{ __('Learn more') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="time-tracking-help-state">
|
||||
<div class="time-tracking-info">
|
||||
<h4>
|
||||
{{ __('Track time with quick actions') }}
|
||||
</h4>
|
||||
<p>
|
||||
{{ __('Quick actions can be used in the issues description and comment boxes.') }}
|
||||
</p>
|
||||
<p v-html="estimateText">
|
||||
</p>
|
||||
<p v-html="spendText">
|
||||
</p>
|
||||
<a
|
||||
class="btn btn-default learn-more-button"
|
||||
:href="href"
|
||||
>
|
||||
{{ __('Learn more') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import timeTrackingHelpState from './help_state';
|
||||
import TimeTrackingHelpState from './help_state.vue';
|
||||
import TimeTrackingCollapsedState from './collapsed_state.vue';
|
||||
import timeTrackingSpentOnlyPane from './spent_only_pane';
|
||||
import timeTrackingNoTrackingPane from './no_tracking_pane';
|
||||
import timeTrackingEstimateOnlyPane from './estimate_only_pane';
|
||||
import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue';
|
||||
import TimeTrackingComparisonPane from './comparison_pane.vue';
|
||||
|
||||
import eventHub from '../../event_hub';
|
||||
|
@ -12,11 +12,11 @@ export default {
|
|||
name: 'IssuableTimeTracker',
|
||||
components: {
|
||||
TimeTrackingCollapsedState,
|
||||
'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
|
||||
TimeTrackingEstimateOnlyPane,
|
||||
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
|
||||
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
|
||||
TimeTrackingComparisonPane,
|
||||
'time-tracking-help-state': timeTrackingHelpState,
|
||||
TimeTrackingHelpState,
|
||||
},
|
||||
props: {
|
||||
time_estimate: {
|
||||
|
|
|
@ -7,7 +7,10 @@ export default {
|
|||
statusIcon,
|
||||
},
|
||||
props: {
|
||||
mr: { type: Object, required: true },
|
||||
mr: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -20,13 +23,14 @@ export default {
|
|||
/>
|
||||
<div class="media-body space-children">
|
||||
<span class="bold">
|
||||
There are unresolved discussions. Please resolve these discussions
|
||||
{{ s__("mrWidget|There are unresolved discussions. Please resolve these discussions") }}
|
||||
</span>
|
||||
<a
|
||||
v-if="mr.createIssueToResolveDiscussionsPath"
|
||||
:href="mr.createIssueToResolveDiscussionsPath"
|
||||
class="btn btn-secondary btn-xs js-create-issue">
|
||||
Create an issue to resolve them later
|
||||
class="btn btn-secondary btn-xs js-create-issue"
|
||||
>
|
||||
{{ s__("mrWidget|Create an issue to resolve them later") }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -27,20 +27,22 @@
|
|||
$(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
|
||||
},
|
||||
methods: {
|
||||
isMarkdownForm(form) {
|
||||
return form && !form.find('.js-vue-markdown-field').length;
|
||||
isValid(form) {
|
||||
return !form ||
|
||||
form.find('.js-vue-markdown-field').length ||
|
||||
$(this.$el).closest('form') === form[0];
|
||||
},
|
||||
|
||||
previewMarkdownTab(event, form) {
|
||||
if (event.target.blur) event.target.blur();
|
||||
if (this.isMarkdownForm(form)) return;
|
||||
if (!this.isValid(form)) return;
|
||||
|
||||
this.$emit('preview-markdown');
|
||||
},
|
||||
|
||||
writeMarkdownTab(event, form) {
|
||||
if (event.target.blur) event.target.blur();
|
||||
if (this.isMarkdownForm(form)) return;
|
||||
if (!this.isValid(form)) return;
|
||||
|
||||
this.$emit('write-markdown');
|
||||
},
|
||||
|
|
|
@ -813,7 +813,6 @@
|
|||
}
|
||||
|
||||
.discussion-notes {
|
||||
padding: 0 $gl-padding $gl-padding;
|
||||
min-height: 35px;
|
||||
|
||||
&:first-child {
|
||||
|
|
|
@ -154,26 +154,10 @@
|
|||
a {
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
margin-right: 0;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
a {
|
||||
border: 0;
|
||||
border-bottom: 2px solid $link-underline-blue;
|
||||
margin-right: 0;
|
||||
color: $black;
|
||||
|
||||
&:hover {
|
||||
border-bottom: 2px solid $link-underline-blue;
|
||||
}
|
||||
}
|
||||
&.active > a {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,7 +173,11 @@
|
|||
}
|
||||
|
||||
.discussion-form {
|
||||
padding-top: $gl-padding-top;
|
||||
background-color: $white-light;
|
||||
}
|
||||
|
||||
.discussion-form-container {
|
||||
padding: $gl-padding-top $gl-padding $gl-padding;
|
||||
}
|
||||
|
||||
.discussion-notes .disabled-comment {
|
||||
|
@ -233,7 +237,12 @@
|
|||
.discussion-body,
|
||||
.diff-file {
|
||||
.discussion-reply-holder {
|
||||
padding-top: $gl-padding;
|
||||
background-color: $white-light;
|
||||
padding: 10px 16px;
|
||||
|
||||
&.is-replying {
|
||||
padding-bottom: $gl-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ ul.notes {
|
|||
}
|
||||
|
||||
.timeline-entry-inner {
|
||||
padding: $gl-padding 0;
|
||||
padding: $gl-padding $gl-btn-padding;
|
||||
border-bottom: 1px solid $white-normal;
|
||||
}
|
||||
|
||||
|
@ -94,6 +94,12 @@ ul.notes {
|
|||
}
|
||||
}
|
||||
|
||||
&.note-discussion {
|
||||
.timeline-entry-inner {
|
||||
padding: $gl-padding 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.editing-spinner {
|
||||
display: none;
|
||||
}
|
||||
|
@ -346,8 +352,6 @@ ul.notes {
|
|||
}
|
||||
|
||||
.discussion-notes {
|
||||
background-color: $white-light;
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid $white-normal;
|
||||
margin-top: 20px;
|
||||
|
@ -359,6 +363,10 @@ ul.notes {
|
|||
}
|
||||
}
|
||||
|
||||
.notes {
|
||||
background-color: $white-light;
|
||||
}
|
||||
|
||||
a code {
|
||||
top: 0;
|
||||
margin-right: 0;
|
||||
|
@ -639,6 +647,8 @@ ul.notes {
|
|||
border-bottom: 1px solid $white-normal;
|
||||
|
||||
.timeline-entry-inner {
|
||||
padding-left: $gl-padding;
|
||||
padding-right: $gl-padding;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -344,7 +344,6 @@
|
|||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.stage-column {
|
||||
|
@ -495,17 +494,12 @@
|
|||
svg {
|
||||
fill: $gl-text-color-secondary;
|
||||
position: relative;
|
||||
left: 1px;
|
||||
top: -1px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&.play {
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
left: 3px;
|
||||
left: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -935,11 +935,6 @@ pre.light-well {
|
|||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-toggle {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.flash-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def application_setting_params
|
||||
params[:application_setting] ||= {}
|
||||
import_sources = params[:application_setting][:import_sources]
|
||||
|
||||
if import_sources.nil?
|
||||
params[:application_setting][:import_sources] = []
|
||||
else
|
||||
|
|
21
app/controllers/concerns/checks_collaboration.rb
Normal file
21
app/controllers/concerns/checks_collaboration.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module ChecksCollaboration
|
||||
def can_collaborate_with_project?(project, ref: nil)
|
||||
return true if can?(current_user, :push_code, project)
|
||||
|
||||
can_create_merge_request =
|
||||
can?(current_user, :create_merge_request_in, project) &&
|
||||
current_user.already_forked?(project)
|
||||
|
||||
can_create_merge_request ||
|
||||
user_access(project).can_push_to_branch?(ref)
|
||||
end
|
||||
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
# enabling this so we can easily cache the user access value as it might be
|
||||
# used across multiple calls in the view
|
||||
def user_access(project)
|
||||
@user_access ||= {}
|
||||
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
end
|
|
@ -41,7 +41,7 @@ module NotesActions
|
|||
@note = Notes::CreateService.new(note_project, current_user, create_params).execute
|
||||
|
||||
if @note.is_a?(Note)
|
||||
Notes::RenderService.new(current_user).execute([@note], @project)
|
||||
Notes::RenderService.new(current_user).execute([@note])
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -56,7 +56,7 @@ module NotesActions
|
|||
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
|
||||
|
||||
if @note.is_a?(Note)
|
||||
Notes::RenderService.new(current_user).execute([@note], @project)
|
||||
Notes::RenderService.new(current_user).execute([@note])
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
|
|
|
@ -4,7 +4,7 @@ module RendersNotes
|
|||
preload_noteable_for_regular_notes(notes)
|
||||
preload_max_access_for_authors(notes, @project)
|
||||
preload_first_time_contribution_for_authors(noteable, notes)
|
||||
Notes::RenderService.new(current_user).execute(notes, @project)
|
||||
Notes::RenderService.new(current_user).execute(notes)
|
||||
|
||||
notes
|
||||
end
|
||||
|
|
|
@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController
|
|||
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
|
||||
.to_a
|
||||
|
||||
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
|
||||
Events::RenderService
|
||||
.new(current_user)
|
||||
.execute(@events, atom_request: request.format.atom?)
|
||||
end
|
||||
|
||||
def user_actions
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class Projects::ApplicationController < ApplicationController
|
||||
include RoutableActions
|
||||
include ChecksCollaboration
|
||||
|
||||
skip_before_action :authenticate_user!
|
||||
before_action :project
|
||||
|
@ -31,14 +32,6 @@ class Projects::ApplicationController < ApplicationController
|
|||
@repository ||= project.repository
|
||||
end
|
||||
|
||||
def can_collaborate_with_project?(project = nil, ref: nil)
|
||||
project ||= @project
|
||||
|
||||
can?(current_user, :push_code, project) ||
|
||||
(current_user && current_user.already_forked?(project)) ||
|
||||
user_access(project).can_push_to_branch?(ref)
|
||||
end
|
||||
|
||||
def authorize_action!(action)
|
||||
unless can?(current_user, action, project)
|
||||
return access_denied!
|
||||
|
@ -91,9 +84,4 @@ class Projects::ApplicationController < ApplicationController
|
|||
def check_issues_available!
|
||||
return render_404 unless @project.feature_available?(:issues, current_user)
|
||||
end
|
||||
|
||||
def user_access(project)
|
||||
@user_access ||= {}
|
||||
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,6 +34,7 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
|
||||
def pipelines
|
||||
@pipelines = @commit.pipelines.order(id: :desc)
|
||||
@pipelines = @pipelines.where(ref: params[:ref]) if params[:ref]
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
|
|
@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
before_action :authorize_update_issuable!, only: [:edit, :update, :move]
|
||||
|
||||
# Allow create a new branch and empty WIP merge request from current issue
|
||||
before_action :authorize_create_merge_request!, only: [:create_merge_request]
|
||||
before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
|
||||
|
||||
respond_to :html
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
|
|||
|
||||
skip_before_action :merge_request
|
||||
before_action :whitelist_query_limiting, only: [:create]
|
||||
before_action :authorize_create_merge_request!
|
||||
before_action :authorize_create_merge_request_from!
|
||||
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
|
||||
before_action :build_merge_request, except: [:create]
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController
|
|||
private
|
||||
|
||||
def render_json_with_notes_serializer
|
||||
Notes::RenderService.new(current_user).execute([note], project)
|
||||
Notes::RenderService.new(current_user).execute([note])
|
||||
|
||||
render json: note_serializer.represent(note)
|
||||
end
|
||||
|
|
|
@ -159,7 +159,10 @@ class IssuableFinder
|
|||
finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
|
||||
GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute
|
||||
else
|
||||
ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
|
||||
opts = { current_user: current_user }
|
||||
opts[:project_ids_relation] = item_project_ids(items) if items
|
||||
|
||||
ProjectsFinder.new(opts).execute
|
||||
end
|
||||
|
||||
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
|
||||
|
@ -316,9 +319,9 @@ class IssuableFinder
|
|||
def by_project(items)
|
||||
items =
|
||||
if project?
|
||||
items.of_projects(projects(items)).references_project
|
||||
elsif projects(items)
|
||||
items.merge(projects(items).reorder(nil)).join_project
|
||||
items.of_projects(projects).references_project
|
||||
elsif projects
|
||||
items.merge(projects.reorder(nil)).join_project
|
||||
else
|
||||
items.none
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder
|
|||
if @source_project.fork_network
|
||||
@source_project.fork_network.projects
|
||||
.public_or_visible_to_user(current_user)
|
||||
.non_archived
|
||||
.with_feature_available_for_user(:merge_requests, current_user)
|
||||
else
|
||||
Project.where(id: source_project)
|
||||
|
|
|
@ -59,7 +59,7 @@ module BlobHelper
|
|||
button_tag label, class: "#{common_classes} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
|
||||
elsif can_modify_blob?(blob, project, ref)
|
||||
button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
|
||||
elsif can?(current_user, :fork_project, project)
|
||||
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
|
||||
edit_fork_button_tag(common_classes, project, label, edit_modify_file_fork_params(action), action)
|
||||
end
|
||||
end
|
||||
|
@ -280,7 +280,7 @@ module BlobHelper
|
|||
options << link_to("submit an issue", new_project_issue_path(project))
|
||||
end
|
||||
|
||||
merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
|
||||
merge_project = merge_request_source_project_for_project(@project)
|
||||
if merge_project
|
||||
options << link_to("create a merge request", project_new_merge_request_path(project))
|
||||
end
|
||||
|
@ -334,7 +334,7 @@ module BlobHelper
|
|||
# Web IDE (Beta) requires the user to have this feature enabled
|
||||
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
|
||||
edit_link_tag(text, edit_path, common_classes)
|
||||
elsif current_user && can?(current_user, :fork_project, project)
|
||||
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
|
||||
edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -94,7 +94,7 @@ module CiStatusHelper
|
|||
|
||||
def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
|
||||
project = pipeline_status.project
|
||||
path = pipelines_project_commit_path(project, pipeline_status.sha)
|
||||
path = pipelines_project_commit_path(project, pipeline_status.sha, ref: pipeline_status.ref)
|
||||
|
||||
render_status_with_link(
|
||||
'commit',
|
||||
|
@ -105,7 +105,7 @@ module CiStatusHelper
|
|||
|
||||
def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
|
||||
project = commit.project
|
||||
path = pipelines_project_commit_path(project, commit)
|
||||
path = pipelines_project_commit_path(project, commit, ref: ref)
|
||||
|
||||
render_status_with_link(
|
||||
'commit',
|
||||
|
|
|
@ -163,7 +163,7 @@ module CommitsHelper
|
|||
tooltip = "#{action.capitalize} this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
|
||||
btn_class = "btn btn-#{btn_class}" unless btn_class.nil?
|
||||
|
||||
if can_collaborate_with_project?
|
||||
if can_collaborate_with_project?(@project)
|
||||
link_to action.capitalize, "#modal-#{action}-commit", 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
|
||||
elsif can?(current_user, :fork_project, @project)
|
||||
continue_params = {
|
||||
|
|
|
@ -3,7 +3,7 @@ module CompareHelper
|
|||
from.present? &&
|
||||
to.present? &&
|
||||
from != to &&
|
||||
can?(current_user, :create_merge_request, project) &&
|
||||
can?(current_user, :create_merge_request_from, project) &&
|
||||
project.repository.branch_exists?(from) &&
|
||||
project.repository.branch_exists?(to)
|
||||
end
|
||||
|
|
|
@ -82,8 +82,8 @@ module IssuesHelper
|
|||
names.to_sentence
|
||||
end
|
||||
|
||||
def award_state_class(awards, current_user)
|
||||
if !current_user
|
||||
def award_state_class(awardable, awards, current_user)
|
||||
if !can?(current_user, :award_emoji, awardable)
|
||||
"disabled"
|
||||
elsif current_user && awards.find { |a| a.user_id == current_user.id }
|
||||
"active"
|
||||
|
@ -126,6 +126,17 @@ module IssuesHelper
|
|||
link_to link_text, path
|
||||
end
|
||||
|
||||
def show_new_issue_link?(project)
|
||||
return false unless project
|
||||
return false if project.archived?
|
||||
|
||||
# We want to show the link to users that are not signed in, that way they
|
||||
# get directed to the sign-in/sign-up flow and afterwards to the new issue page.
|
||||
return true unless current_user
|
||||
|
||||
can?(current_user, :create_issue, project)
|
||||
end
|
||||
|
||||
# Required for Banzai::Filter::IssueReferenceFilter
|
||||
module_function :url_for_issue
|
||||
module_function :url_for_internal_issue
|
||||
|
|
|
@ -256,7 +256,7 @@ module MarkupHelper
|
|||
return '' unless html.present?
|
||||
|
||||
context.merge!(
|
||||
current_user: (current_user if defined?(current_user)),
|
||||
current_user: (current_user if defined?(current_user)),
|
||||
|
||||
# RelativeLinkFilter
|
||||
commit: @commit,
|
||||
|
|
|
@ -138,6 +138,18 @@ module MergeRequestsHelper
|
|||
end
|
||||
end
|
||||
|
||||
def merge_request_source_project_for_project(project = @project)
|
||||
unless can?(current_user, :create_merge_request_in, project)
|
||||
return nil
|
||||
end
|
||||
|
||||
if can?(current_user, :create_merge_request_from, project)
|
||||
project
|
||||
else
|
||||
current_user.fork_of(project)
|
||||
end
|
||||
end
|
||||
|
||||
def merge_params_ee(merge_request)
|
||||
{}
|
||||
end
|
||||
|
|
|
@ -6,10 +6,6 @@ module NotesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def note_editable?(note)
|
||||
Ability.can_edit_note?(current_user, note)
|
||||
end
|
||||
|
||||
def note_supports_quick_actions?(note)
|
||||
Notes::QuickActionsService.supported?(note)
|
||||
end
|
||||
|
|
|
@ -157,40 +157,6 @@ module ProjectsHelper
|
|||
current_user&.recent_push(@project)
|
||||
end
|
||||
|
||||
def project_feature_access_select(field)
|
||||
# Don't show option "everyone with access" if project is private
|
||||
options = project_feature_options
|
||||
|
||||
level = @project.project_feature.public_send(field) # rubocop:disable GitlabSecurity/PublicSend
|
||||
|
||||
if @project.private?
|
||||
disabled_option = ProjectFeature::ENABLED
|
||||
highest_available_option = ProjectFeature::PRIVATE if level == disabled_option
|
||||
end
|
||||
|
||||
options = options_for_select(
|
||||
options.invert,
|
||||
selected: highest_available_option || level,
|
||||
disabled: disabled_option
|
||||
)
|
||||
|
||||
content_tag :div, class: "select-wrapper" do
|
||||
concat(
|
||||
content_tag(
|
||||
:select,
|
||||
options,
|
||||
name: "project[project_feature_attributes][#{field}]",
|
||||
id: "project_project_feature_attributes_#{field}",
|
||||
class: "pull-right form-control select-control #{repo_children_classes(field)} ",
|
||||
data: { field: field }
|
||||
)
|
||||
)
|
||||
concat(
|
||||
icon('chevron-down')
|
||||
)
|
||||
end.html_safe
|
||||
end
|
||||
|
||||
def link_to_autodeploy_doc
|
||||
link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank'
|
||||
end
|
||||
|
@ -274,16 +240,6 @@ module ProjectsHelper
|
|||
|
||||
private
|
||||
|
||||
def repo_children_classes(field)
|
||||
needs_repo_check = [:merge_requests_access_level, :builds_access_level]
|
||||
return unless needs_repo_check.include?(field)
|
||||
|
||||
classes = "project-repo-select js-repo-select"
|
||||
classes << " disabled" unless @project.feature_available?(:repository, current_user)
|
||||
|
||||
classes
|
||||
end
|
||||
|
||||
def get_project_nav_tabs(project, current_user)
|
||||
nav_tabs = [:home]
|
||||
|
||||
|
@ -447,14 +403,6 @@ module ProjectsHelper
|
|||
filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
|
||||
end
|
||||
|
||||
def project_feature_options
|
||||
{
|
||||
ProjectFeature::DISABLED => s_('ProjectFeature|Disabled'),
|
||||
ProjectFeature::PRIVATE => s_('ProjectFeature|Only team members'),
|
||||
ProjectFeature::ENABLED => s_('ProjectFeature|Everyone with access')
|
||||
}
|
||||
end
|
||||
|
||||
def project_child_container_class(view_path)
|
||||
view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
|
||||
end
|
||||
|
@ -463,20 +411,6 @@ module ProjectsHelper
|
|||
IssuesFinder.new(current_user, project_id: project.id).execute
|
||||
end
|
||||
|
||||
def visibility_select_options(project, selected_level)
|
||||
level_options = Gitlab::VisibilityLevel.values.each_with_object([]) do |level, level_options|
|
||||
next if restricted_levels.include?(level)
|
||||
|
||||
level_options << [
|
||||
visibility_level_label(level),
|
||||
{ data: { description: visibility_level_description(level, project) } },
|
||||
level
|
||||
]
|
||||
end
|
||||
|
||||
options_for_select(level_options, selected_level)
|
||||
end
|
||||
|
||||
def restricted_levels
|
||||
return [] if current_user.admin?
|
||||
|
||||
|
|
|
@ -46,10 +46,6 @@ class Ability
|
|||
end
|
||||
end
|
||||
|
||||
def can_edit_note?(user, note)
|
||||
allowed?(user, :edit_note, note)
|
||||
end
|
||||
|
||||
def allowed?(user, action, subject = :global, opts = {})
|
||||
if subject.is_a?(Hash)
|
||||
opts, subject = subject, :global
|
||||
|
|
|
@ -19,7 +19,7 @@ class BroadcastMessage < ActiveRecord::Base
|
|||
after_commit :flush_redis_cache
|
||||
|
||||
def self.current
|
||||
messages = Rails.cache.fetch(CACHE_KEY) { current_and_future_messages.to_a }
|
||||
messages = Rails.cache.fetch(CACHE_KEY, expires_in: cache_expires_in) { current_and_future_messages.to_a }
|
||||
|
||||
return messages if messages.empty?
|
||||
|
||||
|
@ -36,6 +36,10 @@ class BroadcastMessage < ActiveRecord::Base
|
|||
where('ends_at > :now', now: Time.zone.now).order_id_asc
|
||||
end
|
||||
|
||||
def self.cache_expires_in
|
||||
nil
|
||||
end
|
||||
|
||||
def active?
|
||||
started? && !ended?
|
||||
end
|
||||
|
|
|
@ -79,11 +79,7 @@ module Awardable
|
|||
end
|
||||
|
||||
def user_can_award?(current_user, name)
|
||||
if user_authored?(current_user)
|
||||
!awardable_votes?(normalize_name(name))
|
||||
else
|
||||
true
|
||||
end
|
||||
awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self)
|
||||
end
|
||||
|
||||
def user_authored?(current_user)
|
||||
|
@ -119,4 +115,12 @@ module Awardable
|
|||
def normalize_name(name)
|
||||
Gitlab::Emoji.normalize_emoji_name(name)
|
||||
end
|
||||
|
||||
def awardable_by_user?(current_user, name)
|
||||
if user_authored?(current_user)
|
||||
!awardable_votes?(normalize_name(name))
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,7 +34,7 @@ class DeployToken < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def has_access_to?(requested_project)
|
||||
project == requested_project
|
||||
active? && project == requested_project
|
||||
end
|
||||
|
||||
# This is temporal. Currently we limit DeployToken
|
||||
|
|
|
@ -110,7 +110,10 @@ class Event < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# Remove this method when removing Gitlab.rails5? code.
|
||||
def subclass_from_attributes(attrs)
|
||||
return super if Gitlab.rails5?
|
||||
|
||||
# Without this Rails will keep calling this method on the returned class,
|
||||
# resulting in an infinite loop.
|
||||
return unless self == Event
|
||||
|
|
|
@ -11,7 +11,7 @@ module Ci
|
|||
end
|
||||
|
||||
condition(:owner_of_job) do
|
||||
can?(:developer_access) && @subject.triggered_by?(@user)
|
||||
@subject.triggered_by?(@user)
|
||||
end
|
||||
|
||||
rule { protected_ref }.policy do
|
||||
|
@ -19,6 +19,6 @@ module Ci
|
|||
prevent :erase_build
|
||||
end
|
||||
|
||||
rule { can?(:master_access) | owner_of_job }.enable :erase_build
|
||||
rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,23 +7,17 @@ module Ci
|
|||
end
|
||||
|
||||
condition(:owner_of_schedule) do
|
||||
can?(:developer_access) && pipeline_schedule.owned_by?(@user)
|
||||
pipeline_schedule.owned_by?(@user)
|
||||
end
|
||||
|
||||
condition(:non_owner_of_schedule) do
|
||||
!pipeline_schedule.owned_by?(@user)
|
||||
end
|
||||
rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
|
||||
|
||||
rule { can?(:developer_access) }.policy do
|
||||
enable :play_pipeline_schedule
|
||||
end
|
||||
|
||||
rule { can?(:master_access) | owner_of_schedule }.policy do
|
||||
rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
|
||||
enable :update_pipeline_schedule
|
||||
enable :admin_pipeline_schedule
|
||||
end
|
||||
|
||||
rule { can?(:master_access) & non_owner_of_schedule }.policy do
|
||||
rule { can?(:admin_pipeline_schedule) & ~owner_of_schedule }.policy do
|
||||
enable :take_ownership_pipeline_schedule
|
||||
end
|
||||
|
||||
|
|
|
@ -18,9 +18,7 @@ class IssuablePolicy < BasePolicy
|
|||
|
||||
rule { locked & ~is_project_member }.policy do
|
||||
prevent :create_note
|
||||
prevent :update_note
|
||||
prevent :admin_note
|
||||
prevent :resolve_note
|
||||
prevent :edit_note
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,26 +1,21 @@
|
|||
class NotePolicy < BasePolicy
|
||||
delegate { @subject.project }
|
||||
delegate { @subject.noteable if @subject.noteable.lockable? }
|
||||
delegate { @subject.noteable if DeclarativePolicy.has_policy?(@subject.noteable) }
|
||||
|
||||
condition(:is_author) { @user && @subject.author == @user }
|
||||
condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? }
|
||||
condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
|
||||
|
||||
condition(:editable, scope: :subject) { @subject.editable? }
|
||||
|
||||
rule { ~editable | anonymous }.prevent :edit_note
|
||||
|
||||
rule { is_author | admin }.enable :edit_note
|
||||
rule { can?(:master_access) }.enable :edit_note
|
||||
rule { ~editable }.prevent :admin_note
|
||||
|
||||
rule { is_author }.policy do
|
||||
enable :read_note
|
||||
enable :update_note
|
||||
enable :admin_note
|
||||
enable :resolve_note
|
||||
end
|
||||
|
||||
rule { for_merge_request & is_noteable_author }.policy do
|
||||
rule { is_noteable_author }.policy do
|
||||
enable :resolve_note
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy
|
|||
end
|
||||
|
||||
rule { anonymous }.prevent :comment_personal_snippet
|
||||
|
||||
rule { can?(:comment_personal_snippet) }.enable :award_emoji
|
||||
end
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
class ProjectPolicy < BasePolicy
|
||||
def self.create_read_update_admin(name)
|
||||
[
|
||||
:"create_#{name}",
|
||||
:"read_#{name}",
|
||||
:"update_#{name}",
|
||||
:"admin_#{name}"
|
||||
]
|
||||
end
|
||||
extend ClassMethods
|
||||
|
||||
READONLY_FEATURES_WHEN_ARCHIVED = %i[
|
||||
issue
|
||||
list
|
||||
merge_request
|
||||
label
|
||||
milestone
|
||||
project_snippet
|
||||
wiki
|
||||
note
|
||||
pipeline
|
||||
pipeline_schedule
|
||||
build
|
||||
trigger
|
||||
environment
|
||||
deployment
|
||||
commit_status
|
||||
container_image
|
||||
pages
|
||||
cluster
|
||||
].freeze
|
||||
|
||||
desc "User is a project owner"
|
||||
condition :owner do
|
||||
|
@ -15,7 +29,7 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
desc "Project has public builds enabled"
|
||||
condition(:public_builds, scope: :subject) { project.public_builds? }
|
||||
condition(:public_builds, scope: :subject, score: 0) { project.public_builds? }
|
||||
|
||||
# For guest access we use #team_member? so we can use
|
||||
# project.members, which gets cached in subject scope.
|
||||
|
@ -35,7 +49,7 @@ class ProjectPolicy < BasePolicy
|
|||
condition(:master) { team_access_level >= Gitlab::Access::MASTER }
|
||||
|
||||
desc "Project is public"
|
||||
condition(:public_project, scope: :subject) { project.public? }
|
||||
condition(:public_project, scope: :subject, score: 0) { project.public? }
|
||||
|
||||
desc "Project is visible to internal users"
|
||||
condition(:internal_access) do
|
||||
|
@ -46,7 +60,7 @@ class ProjectPolicy < BasePolicy
|
|||
condition(:group_member, scope: :subject) { project_group_member? }
|
||||
|
||||
desc "Project is archived"
|
||||
condition(:archived, scope: :subject) { project.archived? }
|
||||
condition(:archived, scope: :subject, score: 0) { project.archived? }
|
||||
|
||||
condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
|
||||
|
||||
|
@ -56,10 +70,10 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
desc "Project has an external wiki"
|
||||
condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
|
||||
condition(:has_external_wiki, scope: :subject, score: 0) { project.has_external_wiki? }
|
||||
|
||||
desc "Project has request access enabled"
|
||||
condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
|
||||
condition(:request_access_enabled, scope: :subject, score: 0) { project.request_access_enabled }
|
||||
|
||||
desc "Has merge requests allowing pushes to user"
|
||||
condition(:has_merge_requests_allowing_pushes, scope: :subject) do
|
||||
|
@ -126,6 +140,7 @@ class ProjectPolicy < BasePolicy
|
|||
|
||||
rule { can?(:guest_access) }.policy do
|
||||
enable :read_project
|
||||
enable :create_merge_request_in
|
||||
enable :read_board
|
||||
enable :read_list
|
||||
enable :read_wiki
|
||||
|
@ -140,6 +155,7 @@ class ProjectPolicy < BasePolicy
|
|||
enable :create_note
|
||||
enable :upload_file
|
||||
enable :read_cycle_analytics
|
||||
enable :award_emoji
|
||||
end
|
||||
|
||||
# These abilities are not allowed to admins that are not members of the project,
|
||||
|
@ -197,7 +213,7 @@ class ProjectPolicy < BasePolicy
|
|||
enable :create_pipeline
|
||||
enable :update_pipeline
|
||||
enable :create_pipeline_schedule
|
||||
enable :create_merge_request
|
||||
enable :create_merge_request_from
|
||||
enable :create_wiki
|
||||
enable :push_code
|
||||
enable :resolve_note
|
||||
|
@ -208,7 +224,7 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
rule { can?(:master_access) }.policy do
|
||||
enable :delete_protected_branch
|
||||
enable :push_to_delete_protected_branch
|
||||
enable :update_project_snippet
|
||||
enable :update_environment
|
||||
enable :update_deployment
|
||||
|
@ -231,37 +247,50 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
rule { archived }.policy do
|
||||
prevent :create_merge_request
|
||||
prevent :push_code
|
||||
prevent :delete_protected_branch
|
||||
prevent :update_merge_request
|
||||
prevent :admin_merge_request
|
||||
prevent :push_to_delete_protected_branch
|
||||
prevent :request_access
|
||||
prevent :upload_file
|
||||
prevent :resolve_note
|
||||
prevent :create_merge_request_from
|
||||
prevent :create_merge_request_in
|
||||
prevent :award_emoji
|
||||
|
||||
READONLY_FEATURES_WHEN_ARCHIVED.each do |feature|
|
||||
prevent(*create_update_admin_destroy(feature))
|
||||
end
|
||||
end
|
||||
|
||||
rule { issues_disabled }.policy do
|
||||
prevent(*create_read_update_admin_destroy(:issue))
|
||||
end
|
||||
|
||||
rule { merge_requests_disabled | repository_disabled }.policy do
|
||||
prevent(*create_read_update_admin(:merge_request))
|
||||
prevent :create_merge_request_in
|
||||
prevent :create_merge_request_from
|
||||
prevent(*create_read_update_admin_destroy(:merge_request))
|
||||
end
|
||||
|
||||
rule { issues_disabled & merge_requests_disabled }.policy do
|
||||
prevent(*create_read_update_admin(:label))
|
||||
prevent(*create_read_update_admin(:milestone))
|
||||
prevent(*create_read_update_admin_destroy(:label))
|
||||
prevent(*create_read_update_admin_destroy(:milestone))
|
||||
end
|
||||
|
||||
rule { snippets_disabled }.policy do
|
||||
prevent(*create_read_update_admin(:project_snippet))
|
||||
prevent(*create_read_update_admin_destroy(:project_snippet))
|
||||
end
|
||||
|
||||
rule { wiki_disabled & ~has_external_wiki }.policy do
|
||||
prevent(*create_read_update_admin(:wiki))
|
||||
prevent(*create_read_update_admin_destroy(:wiki))
|
||||
prevent(:download_wiki_code)
|
||||
end
|
||||
|
||||
rule { builds_disabled | repository_disabled }.policy do
|
||||
prevent(*create_read_update_admin(:build))
|
||||
prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline]))
|
||||
prevent(*create_read_update_admin(:pipeline_schedule))
|
||||
prevent(*create_read_update_admin(:environment))
|
||||
prevent(*create_read_update_admin(:deployment))
|
||||
prevent(*create_update_admin_destroy(:pipeline))
|
||||
prevent(*create_read_update_admin_destroy(:build))
|
||||
prevent(*create_read_update_admin_destroy(:pipeline_schedule))
|
||||
prevent(*create_read_update_admin_destroy(:environment))
|
||||
prevent(*create_read_update_admin_destroy(:deployment))
|
||||
end
|
||||
|
||||
rule { repository_disabled }.policy do
|
||||
|
@ -272,7 +301,7 @@ class ProjectPolicy < BasePolicy
|
|||
end
|
||||
|
||||
rule { container_registry_disabled }.policy do
|
||||
prevent(*create_read_update_admin(:container_image))
|
||||
prevent(*create_read_update_admin_destroy(:container_image))
|
||||
end
|
||||
|
||||
rule { anonymous & ~public_project }.prevent_all
|
||||
|
@ -314,13 +343,6 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_pipeline_schedule
|
||||
end
|
||||
|
||||
rule { issues_disabled }.policy do
|
||||
prevent :create_issue
|
||||
prevent :update_issue
|
||||
prevent :admin_issue
|
||||
prevent :read_issue
|
||||
end
|
||||
|
||||
# These rules are included to allow maintainers of projects to push to certain
|
||||
# to run pipelines for the branches they have access to.
|
||||
rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do
|
||||
|
|
19
app/policies/project_policy/class_methods.rb
Normal file
19
app/policies/project_policy/class_methods.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class ProjectPolicy
|
||||
module ClassMethods
|
||||
def create_read_update_admin_destroy(name)
|
||||
[
|
||||
:"read_#{name}",
|
||||
*create_update_admin_destroy(name)
|
||||
]
|
||||
end
|
||||
|
||||
def create_update_admin_destroy(name)
|
||||
[
|
||||
:"create_#{name}",
|
||||
:"update_#{name}",
|
||||
:"admin_#{name}",
|
||||
:"destroy_#{name}"
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
|
|||
include GitlabRoutingHelper
|
||||
include MarkupHelper
|
||||
include TreeHelper
|
||||
include ChecksCollaboration
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
presents :merge_request
|
||||
|
@ -152,11 +153,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
|
|||
end
|
||||
|
||||
def can_revert_on_current_merge_request?
|
||||
user_can_collaborate_with_project? && cached_can_be_reverted?
|
||||
can_collaborate_with_project?(project) && cached_can_be_reverted?
|
||||
end
|
||||
|
||||
def can_cherry_pick_on_current_merge_request?
|
||||
user_can_collaborate_with_project? && can_be_cherry_picked?
|
||||
can_collaborate_with_project?(project) && can_be_cherry_picked?
|
||||
end
|
||||
|
||||
def can_push_to_source_branch?
|
||||
|
@ -195,12 +196,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
|
|||
end.sort.to_sentence
|
||||
end
|
||||
|
||||
def user_can_collaborate_with_project?
|
||||
can?(current_user, :push_code, project) ||
|
||||
(current_user && current_user.already_forked?(project)) ||
|
||||
can_push_to_source_branch?
|
||||
end
|
||||
|
||||
def user_can_fork_project?
|
||||
can?(current_user, :fork_project, project)
|
||||
end
|
||||
|
|
|
@ -29,6 +29,10 @@ class IssueEntity < IssuableEntity
|
|||
expose :can_update do |issue|
|
||||
can?(request.current_user, :update_issue, issue)
|
||||
end
|
||||
|
||||
expose :can_award_emoji do |issue|
|
||||
can?(request.current_user, :award_emoji, issue)
|
||||
end
|
||||
end
|
||||
|
||||
expose :create_note_path do |issue|
|
||||
|
|
|
@ -15,7 +15,11 @@ class NoteEntity < API::Entities::Note
|
|||
|
||||
expose :current_user do
|
||||
expose :can_edit do |note|
|
||||
Ability.can_edit_note?(request.current_user, note)
|
||||
Ability.allowed?(request.current_user, :admin_note, note)
|
||||
end
|
||||
|
||||
expose :can_award_emoji do |note|
|
||||
Ability.allowed?(request.current_user, :award_emoji, note)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -149,7 +149,8 @@ module Auth
|
|||
def deploy_token_can_pull?(requested_project)
|
||||
has_authentication_ability?(:read_container_image) &&
|
||||
current_user.is_a?(DeployToken) &&
|
||||
current_user.has_access_to?(requested_project)
|
||||
current_user.has_access_to?(requested_project) &&
|
||||
current_user.read_registry?
|
||||
end
|
||||
|
||||
##
|
||||
|
|
|
@ -4,9 +4,6 @@ module Ci
|
|||
class RegisterJobService
|
||||
attr_reader :runner
|
||||
|
||||
JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30].freeze
|
||||
JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
|
||||
|
||||
Result = Struct.new(:build, :valid?)
|
||||
|
||||
def initialize(runner)
|
||||
|
@ -107,22 +104,10 @@ module Ci
|
|||
end
|
||||
|
||||
def register_success(job)
|
||||
labels = { shared_runner: runner.shared?,
|
||||
jobs_running_for_project: jobs_running_for_project(job) }
|
||||
|
||||
job_queue_duration_seconds.observe(labels, Time.now - job.queued_at)
|
||||
job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
|
||||
attempt_counter.increment
|
||||
end
|
||||
|
||||
def jobs_running_for_project(job)
|
||||
return '+Inf' unless runner.shared?
|
||||
|
||||
# excluding currently started job
|
||||
running_jobs_count = job.project.builds.running.where(runner: Ci::Runner.shared)
|
||||
.limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1
|
||||
running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+"
|
||||
end
|
||||
|
||||
def failed_attempt_counter
|
||||
@failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
|
||||
end
|
||||
|
@ -132,7 +117,7 @@ module Ci
|
|||
end
|
||||
|
||||
def job_queue_duration_seconds
|
||||
@job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time', {}, JOB_QUEUE_DURATION_SECONDS_BUCKETS)
|
||||
@job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
module Events
|
||||
class RenderService < BaseRenderer
|
||||
def execute(events, atom_request: false)
|
||||
events.map(&:note).compact.group_by(&:project).each do |project, notes|
|
||||
render_notes(notes, project, atom_request)
|
||||
end
|
||||
notes = events.map(&:note).compact
|
||||
|
||||
render_notes(notes, atom_request)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_notes(notes, project, atom_request)
|
||||
Notes::RenderService.new(current_user).execute(notes, project, render_options(atom_request))
|
||||
def render_notes(notes, atom_request)
|
||||
Notes::RenderService
|
||||
.new(current_user)
|
||||
.execute(notes, render_options(atom_request))
|
||||
end
|
||||
|
||||
def render_options(atom_request)
|
||||
|
|
|
@ -71,8 +71,8 @@ module MergeRequests
|
|||
params.delete(:source_project_id)
|
||||
params.delete(:target_project_id)
|
||||
|
||||
unless can?(current_user, :read_project, @source_project) &&
|
||||
can?(current_user, :read_project, @project)
|
||||
unless can?(current_user, :create_merge_request_from, @source_project) &&
|
||||
can?(current_user, :create_merge_request_in, @project)
|
||||
|
||||
raise Gitlab::Access::AccessDeniedError
|
||||
end
|
||||
|
|
|
@ -3,19 +3,18 @@ module Notes
|
|||
# Renders a collection of Note instances.
|
||||
#
|
||||
# notes - The notes to render.
|
||||
# project - The project to use for redacting.
|
||||
# user - The user viewing the notes.
|
||||
|
||||
#
|
||||
# Possible options:
|
||||
#
|
||||
# requested_path - The request path.
|
||||
# project_wiki - The project's wiki.
|
||||
# ref - The current Git reference.
|
||||
# only_path - flag to turn relative paths into absolute ones.
|
||||
# xhtml - flag to save the html in XHTML
|
||||
def execute(notes, project, **opts)
|
||||
renderer = Banzai::ObjectRenderer.new(project, current_user, **opts)
|
||||
|
||||
renderer.render(notes, :note)
|
||||
def execute(notes, options = {})
|
||||
Banzai::ObjectRenderer
|
||||
.new(user: current_user, redaction_context: options)
|
||||
.render(notes, :note)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
- if @project.archived?
|
||||
%li
|
||||
%span.light archived:
|
||||
%strong repository is read-only
|
||||
%strong project is read-only
|
||||
|
||||
%li
|
||||
%span.light access:
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
= link_to 'Block', block_admin_user_path(user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put
|
||||
- if user.access_locked?
|
||||
%li
|
||||
= link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
|
||||
= link_to _('Unlock'), unlock_admin_user_path(user), method: :put, data: { confirm: _('Are you sure?') }
|
||||
- if can?(current_user, :destroy_user, user)
|
||||
%li.divider
|
||||
- if user.can_be_removed?
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
|
||||
- awards_sort(grouped_emojis).each do |emoji, awards|
|
||||
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
|
||||
class: [(award_state_class(awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
|
||||
class: [(award_state_class(awardable, awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
|
||||
data: { placement: "bottom", title: award_user_list(awards, current_user) } }
|
||||
= emoji_icon(emoji)
|
||||
%span.award-control-text.js-counter
|
||||
= awards.count
|
||||
|
||||
- if current_user
|
||||
- if can?(current_user, :award_emoji, awardable)
|
||||
.award-menu-holder.js-award-holder
|
||||
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
|
||||
'aria-label': 'Add reaction',
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
%ul.nav-links.nav-tabs.new-session-tabs.single-tab
|
||||
%ul.nav-links.new-session-tabs.single-tab
|
||||
%li.active
|
||||
%a= tab_title
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
%ul.new-session-tabs.nav-links.nav-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) }
|
||||
%ul.nav-links.new-session-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) }
|
||||
- if crowd_enabled?
|
||||
%li.active
|
||||
= link_to "Crowd", "#crowd", 'data-toggle' => 'tab'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' }
|
||||
%ul.nav-links.new-session-tabs{ role: 'tablist' }
|
||||
%li.active{ role: 'presentation' }
|
||||
%a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
|
||||
- if allow_signup?
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
%li.dropdown-bold-header GitLab
|
||||
|
||||
- if @project&.persisted?
|
||||
- create_project_issue = can?(current_user, :create_issue, @project)
|
||||
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
|
||||
- create_project_issue = show_new_issue_link?(@project)
|
||||
- merge_project = merge_request_source_project_for_project(@project)
|
||||
- create_project_snippet = can?(current_user, :create_project_snippet, @project)
|
||||
- if create_project_issue || merge_project || create_project_snippet
|
||||
%li.dropdown-bold-header This project
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
.nav-icon-container
|
||||
= sprite_icon('project')
|
||||
%span.nav-item-name
|
||||
Overview
|
||||
Project
|
||||
|
||||
%ul.sidebar-sub-level-items
|
||||
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#{time_ago_with_tooltip(event.created_at)}
|
||||
|
||||
.flex-right
|
||||
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do
|
||||
#{ _('Create merge request') }
|
||||
- if can?(current_user, :create_merge_request_in, event.project.default_merge_request_target)
|
||||
.flex-right
|
||||
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do
|
||||
#{ _('Create merge request') }
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
- if can_change_visibility_level?(@project, current_user)
|
||||
.select-wrapper
|
||||
= form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select select-control')
|
||||
= icon('chevron-down')
|
||||
- else
|
||||
.info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } }
|
||||
= visibility_level_icon(@project.visibility_level)
|
||||
%strong
|
||||
= visibility_level_label(@project.visibility_level)
|
|
@ -4,7 +4,7 @@
|
|||
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
|
||||
- number_commits_behind = diverging_commit_counts[:behind]
|
||||
- number_commits_ahead = diverging_commit_counts[:ahead]
|
||||
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
|
||||
- merge_project = merge_request_source_project_for_project(@project)
|
||||
%li{ class: "branch-item js-branch-#{branch.name}" }
|
||||
.branch-info
|
||||
.branch-title
|
||||
|
@ -61,7 +61,7 @@
|
|||
title: s_('Branches|The default branch cannot be deleted') }
|
||||
= icon("trash-o")
|
||||
- elsif protected_branch?(@project, branch)
|
||||
- if can?(current_user, :delete_protected_branch, @project)
|
||||
- if can?(current_user, :push_to_delete_protected_branch, @project)
|
||||
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
|
||||
title: s_('Branches|Delete protected branch'),
|
||||
data: { toggle: "modal",
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
- if current_user
|
||||
- can_create_issue = show_new_issue_link?(@project)
|
||||
- can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
|
||||
- can_push_code = can?(current_user, :push_code, @project)
|
||||
- create_mr_from_new_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
|
||||
- merge_project = merge_request_source_project_for_project(@project)
|
||||
|
||||
- show_menu = can_create_issue || can_create_project_snippet || can_push_code || create_mr_from_new_fork || merge_project
|
||||
|
||||
- if show_menu
|
||||
.project-action-button.dropdown.inline
|
||||
%a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') }
|
||||
= icon('plus')
|
||||
= icon("caret-down")
|
||||
%ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
|
||||
- can_create_issue = can?(current_user, :create_issue, @project)
|
||||
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
|
||||
- can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
|
||||
|
||||
- if can_create_issue || merge_project || can_create_project_snippet
|
||||
%li.dropdown-header= _('This project')
|
||||
|
||||
|
@ -20,17 +24,17 @@
|
|||
- if can_create_project_snippet
|
||||
%li= link_to _('New snippet'), new_project_snippet_path(@project)
|
||||
|
||||
- if can?(current_user, :push_code, @project)
|
||||
- if can_push_code
|
||||
%li.dropdown-header= _('This repository')
|
||||
|
||||
- if can?(current_user, :push_code, @project)
|
||||
- if can_push_code
|
||||
%li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
|
||||
- unless @project.empty_repo?
|
||||
%li= link_to _('New branch'), new_project_branch_path(@project)
|
||||
%li= link_to _('New tag'), new_project_tag_path(@project)
|
||||
- elsif current_user && current_user.already_forked?(@project)
|
||||
- elsif can_collaborate_with_project?(@project)
|
||||
%li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
|
||||
- elsif can?(current_user, :fork_project, @project)
|
||||
- elsif create_mr_from_new_fork
|
||||
- continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
|
||||
notice: edit_in_new_fork_notice,
|
||||
notice_now: edit_in_new_fork_notice_now }
|
||||
|
|
|
@ -7,5 +7,6 @@
|
|||
- link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
|
||||
%p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
|
||||
|
||||
.text-center
|
||||
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
|
||||
- if can?(current_user, :create_cluster, @project)
|
||||
.text-center
|
||||
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
- can_collaborate = can_collaborate_with_project?(@project)
|
||||
|
||||
.page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) }
|
||||
.header-main-content
|
||||
= render partial: 'signature', object: @commit.signature
|
||||
|
@ -32,12 +34,13 @@
|
|||
%li.d-block.d-sm-none.d-md-none
|
||||
= link_to project_tree_path(@project, @commit) do
|
||||
#{ _('Browse Files') }
|
||||
- unless @commit.has_been_reverted?(current_user)
|
||||
- if can_collaborate && !@commit.has_been_reverted?(current_user)
|
||||
%li.clearfix
|
||||
= revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
|
||||
%li.clearfix
|
||||
= cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
|
||||
- if can_collaborate_with_project?
|
||||
- if can_collaborate
|
||||
%li.clearfix
|
||||
= cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
|
||||
- if can?(current_user, :push_code, @project)
|
||||
%li.clearfix
|
||||
= link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
|
||||
%li.divider
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
|
||||
.limited-width-notes
|
||||
= render "shared/notes/notes_with_form", :autocomplete => true
|
||||
- if can_collaborate_with_project?
|
||||
- if can_collaborate_with_project?(@project)
|
||||
- %w(revert cherry-pick).each do |type|
|
||||
= render "projects/commit/change", type: type, commit: @commit, title: @commit.title
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
- link = commit_path(project, commit, merge_request: merge_request)
|
||||
- cache_key = [project.full_path,
|
||||
ref,
|
||||
commit.id,
|
||||
Gitlab::CurrentSettings.current_application_settings,
|
||||
@path.presence,
|
||||
|
@ -54,7 +55,7 @@
|
|||
- if commit.status(ref)
|
||||
= render_commit_status(commit, ref: ref)
|
||||
|
||||
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
|
||||
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
|
||||
|
||||
.commit-sha-group
|
||||
.label.label-monospace
|
||||
|
|
|
@ -114,17 +114,18 @@
|
|||
Archive project
|
||||
- if @project.archived?
|
||||
%p
|
||||
Unarchiving the project will mark its repository as active. The project can be committed to.
|
||||
Unarchiving the project will restore people's ability to make changes to it.
|
||||
The repository can be committed to, and issues, comments and other entities can be created.
|
||||
%strong Once active this project shows up in the search and on the dashboard.
|
||||
= link_to 'Unarchive project', unarchive_project_path(@project),
|
||||
data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." },
|
||||
data: { confirm: "Are you sure that you want to unarchive this project?" },
|
||||
method: :post, class: "btn btn-success"
|
||||
- else
|
||||
%p
|
||||
Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches.
|
||||
%strong Archived projects cannot be committed to!
|
||||
Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches.
|
||||
%strong The repository cannot be committed to, and no issues, comments or other entities can be created.
|
||||
= link_to 'Archive project', archive_project_path(@project),
|
||||
data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." },
|
||||
data: { confirm: "Are you sure that you want to archive this project?" },
|
||||
method: :post, class: "btn btn-warning"
|
||||
.sub-section.rename-respository
|
||||
%h4.warning-title
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
= icon('rss')
|
||||
- if @can_bulk_update
|
||||
= button_tag "Edit issues", class: "btn btn-secondary append-right-10 js-bulk-update-toggle"
|
||||
= link_to "New issue", new_project_issue_path(@project,
|
||||
issue: { assignee_id: finder.assignee.try(:id),
|
||||
milestone_id: finder.milestones.first.try(:id) }),
|
||||
class: "btn btn-new",
|
||||
title: "New issue",
|
||||
id: "new_issue_link"
|
||||
- if show_new_issue_link?(@project)
|
||||
= link_to "New issue", new_project_issue_path(@project,
|
||||
issue: { assignee_id: finder.assignee.try(:id),
|
||||
milestone_id: finder.milestones.first.try(:id) }),
|
||||
class: "btn btn-new",
|
||||
title: "New issue",
|
||||
id: "new_issue_link"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
- can_create_merge_request = can?(current_user, :create_merge_request, @project)
|
||||
- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
|
||||
- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
|
||||
|
||||
- if can?(current_user, :push_code, @project)
|
||||
- can_create_merge_request = can?(current_user, :create_merge_request_in, @project)
|
||||
- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
|
||||
- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
|
||||
|
||||
- can_create_path = can_create_branch_project_issue_path(@project, @issue)
|
||||
- create_mr_path = create_merge_request_project_issue_path(@project, @issue, branch_name: @issue.to_branch_name, ref: @project.default_branch)
|
||||
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
- can_update_issue = can?(current_user, :update_issue, @issue)
|
||||
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
|
||||
- can_create_issue = show_new_issue_link?(@project)
|
||||
|
||||
.detail-page-header
|
||||
.detail-page-header-body
|
||||
|
@ -42,16 +43,18 @@
|
|||
%li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen js-btn-issue-action #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
|
||||
- if can_report_spam
|
||||
%li= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
|
||||
- if can_update_issue || can_report_spam
|
||||
%li.divider
|
||||
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
|
||||
- if can_create_issue
|
||||
- if can_update_issue || can_report_spam
|
||||
%li.divider
|
||||
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
|
||||
|
||||
= render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
|
||||
|
||||
- if can_report_spam
|
||||
= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'd-none d-sm-none d-md-block btn btn-grouped btn-spam', title: 'Submit as spam'
|
||||
= link_to new_project_issue_path(@project), class: 'd-none d-sm-none d-md-block btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
|
||||
New issue
|
||||
- if can_create_issue
|
||||
= link_to new_project_issue_path(@project), class: 'd-none d-sm-none d-md-block btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
|
||||
New issue
|
||||
|
||||
.issue-details.issuable-details
|
||||
.detail-page-description.content-block
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
- else
|
||||
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
|
||||
|
||||
- if @build.has_trace?
|
||||
- if @build.running? || @build.has_trace?
|
||||
.build-trace-container.prepend-top-default
|
||||
.top-bar.js-top-bar
|
||||
.js-truncated-info.truncated-info.d-none.d-sm-block.float-left.hidden<
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- @no_container = true
|
||||
- @can_bulk_update = can?(current_user, :admin_merge_request, @project)
|
||||
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
|
||||
- merge_project = merge_request_source_project_for_project(@project)
|
||||
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
|
||||
|
||||
- page_title "Merge Requests"
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
%template{ 'v-else' => '' }
|
||||
= render 'shared/icons/icon_resolve_discussion.svg'
|
||||
|
||||
- if current_user
|
||||
- if can?(current_user, :award_emoji, note)
|
||||
- if note.emoji_awardable?
|
||||
- user_authored = note.user_authored?(current_user)
|
||||
.note-actions-item
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue