Commit b8401cd0 authored by Clement Ho's avatar Clement Ho

Merge branch 'master' into bootstrap4

parents b75f9721 7f01d49b
...@@ -364,10 +364,11 @@ update-tests-metadata: ...@@ -364,10 +364,11 @@ update-tests-metadata:
- rspec_flaky/ - rspec_flaky/
policy: push policy: push
script: 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_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 ${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 - 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 $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' - '[[ -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 - rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
...@@ -735,16 +736,50 @@ codequality: ...@@ -735,16 +736,50 @@ codequality:
expire_in: 1 week expire_in: 1 week
sast: sast:
<<: *except-docs <<: *dedicated-no-docs-no-db-pull-cache-job
image: registry.gitlab.com/gitlab-org/gl-sast:latest image: docker:stable
variables: variables:
CONFIDENCE_LEVEL: 2 SAST_CONFIDENCE_LEVEL: 2
DOCKER_DRIVER: overlay2
allow_failure: true
tags: []
before_script: [] before_script: []
cache: {}
dependencies: []
services:
- docker:stable-dind
script: 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: artifacts:
paths: [gl-sast-report.json] 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: qa:internal:
<<: *dedicated-no-docs-no-db-pull-cache-job <<: *dedicated-no-docs-no-db-pull-cache-job
services: [] services: []
......
...@@ -2,6 +2,24 @@ ...@@ -2,6 +2,24 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. 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) ## 10.6.3 (2018-04-03)
### Security (2 changes) ### Security (2 changes)
......
...@@ -321,6 +321,9 @@ GEM ...@@ -321,6 +321,9 @@ GEM
rubyntlm (~> 0.5) rubyntlm (~> 0.5)
globalid (0.4.1) globalid (0.4.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
goldiloader (2.0.1)
activerecord (>= 4.2, < 5.2)
activesupport (>= 4.2, < 5.2)
gollum-grit_adapter (1.0.1) gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1) gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7) gollum-lib (4.2.7)
...@@ -878,7 +881,7 @@ GEM ...@@ -878,7 +881,7 @@ GEM
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.2) simplecov-html (0.10.2)
slack-notifier (1.5.1) slack-notifier (1.5.1)
spinach (0.10.1) spinach (0.8.10)
colorize colorize
gherkin-ruby (>= 0.3.2) gherkin-ruby (>= 0.3.2)
json json
...@@ -1072,6 +1075,7 @@ DEPENDENCIES ...@@ -1072,6 +1075,7 @@ DEPENDENCIES
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.2)
gitlab-styles (~> 2.3) gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
goldiloader (~> 2.0)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4) gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0) gon (~> 6.1.0)
......
...@@ -55,22 +55,20 @@ ...@@ -55,22 +55,20 @@
}, },
methods: { methods: {
successCallback(resp) { successCallback(resp) {
return resp.json().then((response) => { // depending of the endpoint the response can either bring a `pipelines` key or not.
// depending of the endpoint the response can either bring a `pipelines` key or not. const pipelines = resp.data.pipelines || resp.data;
const pipelines = response.pipelines || response; this.setCommonData(pipelines);
this.setCommonData(pipelines);
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', { const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
detail: { detail: {
pipelines: response, pipelines: resp.data,
}, },
});
// notifiy to update the count in tabs
if (this.$el.parentElement) {
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
}
}); });
// notifiy to update the count in tabs
if (this.$el.parentElement) {
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
}
}, },
}, },
}; };
......
...@@ -22,7 +22,7 @@ export default { ...@@ -22,7 +22,7 @@ export default {
...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']), ...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']),
...mapGetters(['currentMergeRequest']), ...mapGetters(['currentMergeRequest']),
shouldHideEditor() { shouldHideEditor() {
return this.file && this.file.binary && !this.file.raw; return this.file && this.file.binary && !this.file.content;
}, },
editTabCSS() { editTabCSS() {
return { return {
...@@ -212,7 +212,7 @@ export default { ...@@ -212,7 +212,7 @@ export default {
<content-viewer <content-viewer
v-if="shouldHideEditor || file.viewMode === 'preview'" v-if="shouldHideEditor || file.viewMode === 'preview'"
:content="file.content || file.raw" :content="file.content || file.raw"
:path="file.rawPath" :path="file.rawPath || file.path"
:file-size="file.size" :file-size="file.size"
:project-path="file.projectId"/> :project-path="file.projectId"/>
</div> </div>
......
...@@ -1190,12 +1190,12 @@ export default class Notes { ...@@ -1190,12 +1190,12 @@ export default class Notes {
addForm = false; addForm = false;
let lineTypeSelector = ''; let lineTypeSelector = '';
rowCssToAdd = 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 // In parallel view, look inside the correct left/right pane
if (this.isParallelView()) { if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`; lineTypeSelector = `.${lineType}`;
rowCssToAdd = 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`; const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector); let notesContent = targetRow.find(notesContentSelector);
......
...@@ -317,10 +317,10 @@ Please check your network connection and try again.`; ...@@ -317,10 +317,10 @@ Please check your network connection and try again.`;
<note-signed-out-widget v-if="!isLoggedIn" /> <note-signed-out-widget v-if="!isLoggedIn" />
<discussion-locked-widget <discussion-locked-widget
issuable-type="issue" issuable-type="issue"
v-else-if="!canCreateNote" v-else-if="isLocked(getNoteableData) && !canCreateNote"
/> />
<ul <ul
v-else v-else-if="canCreateNote"
class="notes notes-form timeline"> class="notes notes-form timeline">
<li class="timeline-entry"> <li class="timeline-entry">
<div class="timeline-entry-inner"> <div class="timeline-entry-inner">
......
...@@ -40,6 +40,10 @@ export default { ...@@ -40,6 +40,10 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
canAwardEmoji: {
type: Boolean,
required: true,
},
canDelete: { canDelete: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -74,9 +78,6 @@ export default { ...@@ -74,9 +78,6 @@ export default {
shouldShowActionsDropdown() { shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse); return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
}, },
canAddAwardEmoji() {
return this.currentUserId;
},
isAuthoredByCurrentUser() { isAuthoredByCurrentUser() {
return this.authorId === this.currentUserId; return this.authorId === this.currentUserId;
}, },
...@@ -149,7 +150,7 @@ export default { ...@@ -149,7 +150,7 @@ export default {
</button> </button>
</div> </div>
<div <div
v-if="canAddAwardEmoji" v-if="canAwardEmoji"
class="note-actions-item"> class="note-actions-item">
<a <a
v-tooltip v-tooltip
......
...@@ -28,6 +28,10 @@ export default { ...@@ -28,6 +28,10 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
canAwardEmoji: {
type: Boolean,
required: true,
},
}, },
computed: { computed: {
...mapGetters(['getUserData']), ...mapGetters(['getUserData']),
...@@ -67,9 +71,6 @@ export default { ...@@ -67,9 +71,6 @@ export default {
isAuthoredByMe() { isAuthoredByMe() {
return this.noteAuthorId === this.getUserData.id; return this.noteAuthorId === this.getUserData.id;
}, },
isLoggedIn() {
return this.getUserData.id;
},
}, },
created() { created() {
this.emojiSmiling = emojiSmiling; this.emojiSmiling = emojiSmiling;
...@@ -156,7 +157,7 @@ export default { ...@@ -156,7 +157,7 @@ export default {
return title; return title;
}, },
handleAward(awardName) { handleAward(awardName) {
if (!this.isLoggedIn) { if (!this.canAwardEmoji) {
return; return;
} }
...@@ -208,7 +209,7 @@ export default { ...@@ -208,7 +209,7 @@ export default {
</span> </span>
</button> </button>
<div <div
v-if="isLoggedIn" v-if="canAwardEmoji"
class="award-menu-holder"> class="award-menu-holder">
<button <button
v-tooltip v-tooltip
......
...@@ -112,6 +112,7 @@ export default { ...@@ -112,6 +112,7 @@ export default {
:note-author-id="note.author.id" :note-author-id="note.author.id"
:awards="note.award_emoji" :awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path" :toggle-award-path="note.toggle_award_path"
:can-award-emoji="note.current_user.can_award_emoji"
/> />
<note-attachment <note-attachment
v-if="note.attachment" v-if="note.attachment"
......
...@@ -258,7 +258,9 @@ Please check your network connection and try again.`; ...@@ -258,7 +258,9 @@ Please check your network connection and try again.`;
:key="note.id" :key="note.id"
/> />
</ul> </ul>
<div class="discussion-reply-holder"> <div
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder">
<template v-if="!isReplying && canReply"> <template v-if="!isReplying && canReply">
<div <div
class="btn-group d-flex discussion-with-resolve-btn" class="btn-group d-flex discussion-with-resolve-btn"
......
...@@ -177,6 +177,7 @@ export default { ...@@ -177,6 +177,7 @@ export default {
:note-id="note.id" :note-id="note.id"
:access-level="note.human_access" :access-level="note.human_access"
:can-edit="note.current_user.can_edit" :can-edit="note.current_user.can_edit"
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit" :can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse" :can-report-as-abuse="canReportAsAbuse"
:report-abuse-path="note.report_abuse_path" :report-abuse-path="note.report_abuse_path"
......
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit'; import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal'; 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 projectAvatar from '../shared/project_avatar';
import initProjectPermissionsSettings from '../shared/permissions'; import initProjectPermissionsSettings from '../shared/permissions';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new ProjectNew(); // eslint-disable-line no-new initProjectLoadingSpinner();
setupProjectEdit(); setupProjectEdit();
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
......
import ProjectNew from '../shared/project_new'; import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectVisibilitySelector from '../../../project_visibility'; import initProjectVisibilitySelector from '../../../project_visibility';
import initProjectNew from '../../../projects/project_new'; import initProjectNew from '../../../projects/project_new';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new ProjectNew(); // eslint-disable-line no-new initProjectLoadingSpinner();
initProjectVisibilitySelector(); initProjectVisibilitySelector();
initProjectNew.bindEvents(); initProjectNew.bindEvents();
}); });
/* 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));
}
}
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'; ...@@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
* Does that setting the current selected tab in the localStorage * Does that setting the current selected tab in the localStorage
*/ */
export default class SigninTabsMemoizer { 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.currentTabKey = currentTabKey;
this.tabSelector = tabSelector; this.tabSelector = tabSelector;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
......
...@@ -7,10 +7,7 @@ ...@@ -7,10 +7,7 @@
import TablePagination from '../../vue_shared/components/table_pagination.vue'; import TablePagination from '../../vue_shared/components/table_pagination.vue';
import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue'; import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
import NavigationControls from './nav_controls.vue'; import NavigationControls from './nav_controls.vue';
import { import { getParameterByName } from '../../lib/utils/common_utils';
getParameterByName,
parseQueryStringIntoObject,
} from '../../lib/utils/common_utils';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default { export default {
...@@ -19,10 +16,7 @@ ...@@ -19,10 +16,7 @@
NavigationTabs, NavigationTabs,
NavigationControls, NavigationControls,
}, },
mixins: [ mixins: [pipelinesMixin, CIPaginationMixin],
pipelinesMixin,
CIPaginationMixin,
],
props: { props: {
store: { store: {
type: Object, type: Object,
...@@ -147,25 +141,26 @@ ...@@ -147,25 +141,26 @@
*/ */
shouldRenderTabs() { shouldRenderTabs() {
const { stateMap } = this.$options; const { stateMap } = this.$options;
return this.hasMadeRequest && return (
[ this.hasMadeRequest &&
stateMap.loading, [stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
stateMap.tableList, this.stateToRender,
stateMap.error, )
stateMap.emptyTab, );
].includes(this.stateToRender);
}, },
shouldRenderButtons() { shouldRenderButtons() {
return (this.newPipelinePath || return (
this.resetCachePath || (this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
this.ciLintPath) && this.shouldRenderTabs; );
}, },
shouldRenderPagination() { shouldRenderPagination() {
return !this.isLoading && return (
!this.isLoading &&
this.state.pipelines.length && this.state.pipelines.length &&
this.state.pageInfo.total > this.state.pageInfo.perPage; this.state.pageInfo.total > this.state.pageInfo.perPage
);
}, },
emptyTabMessage() { emptyTabMessage() {
...@@ -229,15 +224,13 @@ ...@@ -229,15 +224,13 @@
}, },
methods: { methods: {
successCallback(resp) { successCallback(resp) {
return resp.json().then((response) => { // Because we are polling & the user is interacting verify if the response received
// Because we are polling & the user is interacting verify if the response received // matches the last request made
// matches the last request made if (_.isEqual(resp.config.params, this.requestData)) {
if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) { this.store.storeCount(resp.data.count);
this.store.storeCount(response.count); this.store.storePagination(resp.headers);
this.store.storePagination(resp.headers); this.setCommonData(resp.data.pipelines);
this.setCommonData(response.pipelines); }
}
});
}, },
/** /**
* Handles URL and query parameter changes. * Handles URL and query parameter changes.
...@@ -251,8 +244,9 @@ ...@@ -251,8 +244,9 @@
this.updateInternalState(parameters); this.updateInternalState(parameters);
// fetch new data // fetch new data
return this.service.getPipelines(this.requestData) return this.service
.then((response) => { .getPipelines(this.requestData)
.then(response => {
this.isLoading = false; this.isLoading = false;
this.successCallback(response); this.successCallback(response);
...@@ -271,13 +265,11 @@ ...@@ -271,13 +265,11 @@
handleResetRunnersCache(endpoint) { handleResetRunnersCache(endpoint) {
this.isResetCacheButtonLoading = true; this.isResetCacheButtonLoading = true;
this.service.postAction(endpoint) this.service
.postAction(endpoint)
.then(() => { .then(() => {
this.isResetCacheButtonLoading = false; this.isResetCacheButtonLoading = false;
createFlash( createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
s__('Pipelines|Project cache successfully reset.'),
'notice',
);
}) })
.catch(() => { .catch(() => {
this.isResetCacheButtonLoading = false; this.isResetCacheButtonLoading = false;
......
...@@ -13,16 +13,16 @@ ...@@ -13,16 +13,16 @@
* 3. Merge request widget * 3. Merge request widget
* 4. Commit widget * 4. Commit widget
*/ */
import axios from '../../lib/utils/axios_utils';
import Flash from '../../flash'; import Flash from '../../flash';
import icon from '../../vue_shared/components/icon.vue'; import Icon from '../../vue_shared/components/icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
components: { components: {
loadingIcon, LoadingIcon,
icon, Icon,
}, },
directives: { directives: {
...@@ -88,9 +88,8 @@ ...@@ -88,9 +88,8 @@
}, },
fetchJobs() { fetchJobs() {
this.$http.get(this.stage.dropdown_path) axios.get(this.stage.dropdown_path)
.then(response => response.json()) .then(({ data }) => {
.then((data) => {
this.dropdownContent = data.html; this.dropdownContent = data.html;
this.isLoading = false; this.isLoading = false;
}) })
...@@ -98,8 +97,7 @@ ...@@ -98,8 +97,7 @@
this.closeDropdown(); this.closeDropdown();
this.isLoading = false; this.isLoading = false;
const flash = new Flash('Something went wrong on our end.'); Flash('Something went wrong on our end.');
return flash;
}); });
}, },
......
...@@ -40,10 +40,8 @@ export default class pipelinesMediator { ...@@ -40,10 +40,8 @@ export default class pipelinesMediator {
} }
successCallback(response) { successCallback(response) {
return response.json().then((data) => { this.state.isLoading = false;
this.state.isLoading = false; this.store.storePipeline(response.data);
this.store.storePipeline(data);
});
} }
errorCallback() { errorCallback() {
......
import Vue from 'vue'; import axios from '../../lib/utils/axios_utils';
import VueResource from 'vue-resource';
Vue.use(VueResource);
export default class PipelineService { export default class PipelineService {
constructor(endpoint) { constructor(endpoint) {
this.pipeline = Vue.resource(endpoint); this.pipeline = endpoint;
} }
getPipeline() { getPipeline() {
return this.pipeline.get(); return axios.get(this.pipeline);
} }
// eslint-disable-next-line // eslint-disable-next-line class-methods-use-this
postAction(endpoint) { postAction(endpoint) {
return Vue.http.post(`${endpoint}.json`); return axios.post(`${endpoint}.json`);
} }
} }
/* eslint-disable class-methods-use-this */ import axios from '../../lib/utils/axios_utils';
import Vue from 'vue';
import VueResource from 'vue-resource';
import '../../vue_shared/vue_resource_interceptor';
Vue.use(VueResource);
export default class PipelinesService { export default class PipelinesService {
/** /**
* Commits and merge request endpoints need to be requested with `.json`. * Commits and merge request endpoints need to be requested with `.json`.
* *
* The url provided to request the pipelines in the new merge request * The url provided to request the pipelines in the new merge request
* page already has `.json`. * page already has `.json`.
* *
* @param {String} root * @param {String} root
*/ */
constructor(root) { constructor(root) {
let endpoint;
if (root.indexOf('.json') === -1) { if (root.indexOf('.json') === -1) {
endpoint = `${root}.json`; this.endpoint = `${root}.json`;
} else { } else {
endpoint = root; this.endpoint = root;
} }
this.pipelines = Vue.resource(endpoint);
} }
getPipelines(data = {}) { getPipelines(data = {}) {
const { scope, page } = 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 { ...@@ -38,7 +30,8 @@ export default class PipelinesService {
* @param {String} endpoint * @param {String} endpoint
* @return {Promise} * @return {Promise}
*/ */
// eslint-disable-next-line class-methods-use-this
postAction(endpoint) { postAction(endpoint) {
return Vue.http.post(`${endpoint}.json`); return axios.post(`${endpoint}.json`);
} }
} }
<script>
export default { export default {
name: 'time-tracking-estimate-only-pane', name: 'TimeTrackingEstimateOnlyPane',
props: { props: {
timeEstimateHumanReadable: { timeEstimateHumanReadable: {
type: String, type: String,
required: true, required: true,
}, },
}, },
template: `
<div class="time-tracking-estimate-only-pane">
<span class="bold">
{{ s__('TimeTracking|Estimated:') }}
</span>
{{ timeEstimateHumanReadable }}
</div>
`,
}; };
</script>
<template>
<div class="time-tracking-estimate-only-pane">
<span class="bold">
{{ s__('TimeTracking|Estimated:') }}
</span>
{{ timeEstimateHumanReadable }}
</div>
</template>
<script>
import { sprintf, s__ } from '../../../locale'; import { sprintf, s__ } from '../../../locale';
export default { export default {
name: 'time-tracking-help-state', name: 'TimeTrackingHelpState',
props: { props: {
rootPath: { rootPath: {
type: String, type: String,
...@@ -27,26 +28,28 @@ export default { ...@@ -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>
<script> <script>
import timeTrackingHelpState from './help_state'; import TimeTrackingHelpState from './help_state.vue';
import TimeTrackingCollapsedState from './collapsed_state.vue'; import TimeTrackingCollapsedState from './collapsed_state.vue';
import timeTrackingSpentOnlyPane from './spent_only_pane'; import timeTrackingSpentOnlyPane from './spent_only_pane';
import timeTrackingNoTrackingPane from './no_tracking_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 TimeTrackingComparisonPane from './comparison_pane.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
...@@ -12,11 +12,11 @@ export default { ...@@ -12,11 +12,11 @@ export default {
name: 'IssuableTimeTracker', name: 'IssuableTimeTracker',
components: { components: {
TimeTrackingCollapsedState, TimeTrackingCollapsedState,
'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane, TimeTrackingEstimateOnlyPane,
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane, 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane, 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
TimeTrackingComparisonPane, TimeTrackingComparisonPane,
'time-tracking-help-state': timeTrackingHelpState, TimeTrackingHelpState,
}, },
props: { props: {
time_estimate: { time_estimate: {
......
...@@ -7,7 +7,10 @@ export default { ...@@ -7,7 +7,10 @@ export default {
statusIcon, statusIcon,
}, },
props: { props: {
mr: { type: Object, required: true }, mr: {
type: Object,
required: true,
},
}, },
}; };
</script> </script>
...@@ -20,13 +23,14 @@ export default { ...@@ -20,13 +23,14 @@ export default {
/> />
<div class="media-body space-children"> <div class="media-body space-children">
<span class="bold"> <span class="bold">
There are unresolved discussions. Please resolve these discussions {{ s__("mrWidget|There are unresolved discussions. Please resolve these discussions") }}
</span> </span>
<a <a
v-if="mr.createIssueToResolveDiscussionsPath" v-if="mr.createIssueToResolveDiscussionsPath"
:href="mr.createIssueToResolveDiscussionsPath" :href="mr.createIssueToResolveDiscussionsPath"
class="btn btn-secondary btn-xs js-create-issue"> class="btn btn-secondary btn-xs js-create-issue"
Create an issue to resolve them later >
{{ s__("mrWidget|Create an issue to resolve them later") }}
</a> </a>
</div> </div>
</div> </div>
......
...@@ -27,20 +27,22 @@ ...@@ -27,20 +27,22 @@
$(document).off('markdown-preview:hide.vue', this.writeMarkdownTab); $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
}, },
methods: { methods: {
isMarkdownForm(form) { isValid(form) {
return form && !form.find('.js-vue-markdown-field').length; return !form ||
form.find('.js-vue-markdown-field').length ||
$(this.$el).closest('form') === form[0];
}, },
previewMarkdownTab(event, form) { previewMarkdownTab(event, form) {
if (event.target.blur) event.target.blur(); if (event.target.blur) event.target.blur();
if (this.isMarkdownForm(form)) return; if (!this.isValid(form)) return;
this.$emit('preview-markdown'); this.$emit('preview-markdown');
}, },
writeMarkdownTab(event, form) { writeMarkdownTab(event, form) {
if (event.target.blur) event.target.blur(); if (event.target.blur) event.target.blur();
if (this.isMarkdownForm(form)) return; if (!this.isValid(form)) return;
this.$emit('write-markdown'); this.$emit('write-markdown');
}, },
......
...@@ -813,7 +813,6 @@ ...@@ -813,7 +813,6 @@
} }
.discussion-notes { .discussion-notes {
padding: 0 $gl-padding $gl-padding;
min-height: 35px; min-height: 35px;
&:first-child { &:first-child {
......
...@@ -154,26 +154,10 @@ ...@@ -154,26 +154,10 @@
a { a {
width: 100%; width: 100%;
font-size: 18px; font-size: 18px;
margin-right: 0;
&:hover {
border: 1px solid transparent;
}
} }
&.active { &.active > a {
border-bottom: 1px solid $border-color; cursor: default;
a {
border: 0;
border-bottom: 2px solid $link-underline-blue;
margin-right: 0;
color: $black;
&:hover {
border-bottom: 2px solid $link-underline-blue;
}
}
} }
} }
} }
......
...@@ -173,7 +173,11 @@ ...@@ -173,7 +173,11 @@
} }
.discussion-form { .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 { .discussion-notes .disabled-comment {
...@@ -233,7 +237,12 @@ ...@@ -233,7 +237,12 @@
.discussion-body, .discussion-body,
.diff-file { .diff-file {
.discussion-reply-holder { .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 { ...@@ -47,7 +47,7 @@ ul.notes {
} }
.timeline-entry-inner { .timeline-entry-inner {
padding: $gl-padding 0; padding: $gl-padding $gl-btn-padding;
border-bottom: 1px solid $white-normal; border-bottom: 1px solid $white-normal;
} }
...@@ -94,6 +94,12 @@ ul.notes { ...@@ -94,6 +94,12 @@ ul.notes {
} }
} }
&.note-discussion {
.timeline-entry-inner {
padding: $gl-padding 10px;
}
}
.editing-spinner { .editing-spinner {
display: none; display: none;
} }
...@@ -346,8 +352,6 @@ ul.notes { ...@@ -346,8 +352,6 @@ ul.notes {
} }
.discussion-notes { .discussion-notes {
background-color: $white-light;
&:not(:first-child) { &:not(:first-child) {
border-top: 1px solid $white-normal; border-top: 1px solid $white-normal;
margin-top: 20px; margin-top: 20px;
...@@ -359,6 +363,10 @@ ul.notes { ...@@ -359,6 +363,10 @@ ul.notes {
} }
} }
.notes {
background-color: $white-light;
}
a code { a code {
top: 0; top: 0;
margin-right: 0; margin-right: 0;
...@@ -639,6 +647,8 @@ ul.notes { ...@@ -639,6 +647,8 @@ ul.notes {
border-bottom: 1px solid $white-normal; border-bottom: 1px solid $white-normal;
.timeline-entry-inner { .timeline-entry-inner {
padding-left: $gl-padding;
padding-right: $gl-padding;
border-bottom: 0; border-bottom: 0;
} }
} }
......
...@@ -344,7 +344,6 @@ ...@@ -344,7 +344,6 @@
svg { svg {
vertical-align: middle; vertical-align: middle;
margin-right: 3px;
} }
.stage-column { .stage-column {
...@@ -495,17 +494,12 @@ ...@@ -495,17 +494,12 @@
svg { svg {
fill: $gl-text-color-secondary; fill: $gl-text-color-secondary;
position: relative; position: relative;
left: 1px;
top: -1px; top: -1px;
width: 16px;
height: 16px;
} }
&.play { &.play {
svg { svg {
width: 16px; left: 2px;
height: 16px;
left: 3px;
} }
} }
} }
......
...@@ -935,11 +935,6 @@ pre.light-well { ...@@ -935,11 +935,6 @@ pre.light-well {
} }
} }
.dropdown-menu-toggle {
width: 100%;
max-width: 300px;
}
.flash-container { .flash-container {
padding: 0; padding: 0;
} }
......
...@@ -56,7 +56,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -56,7 +56,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end end
def application_setting_params def application_setting_params
params[:application_setting] ||= {}
import_sources = params[:application_setting][:import_sources] import_sources = params[:application_setting][:import_sources]
if import_sources.nil? if import_sources.nil?
params[:application_setting][:import_sources] = [] params[:application_setting][:import_sources] = []
else else
......
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 ...@@ -41,7 +41,7 @@ module NotesActions
@note = Notes::CreateService.new(note_project, current_user, create_params).execute @note = Notes::CreateService.new(note_project, current_user, create_params).execute
if @note.is_a?(Note) if @note.is_a?(Note)
Notes::RenderService.new(current_user).execute([@note], @project) Notes::RenderService.new(current_user).execute([@note])
end end
respond_to do |format| respond_to do |format|
...@@ -56,7 +56,7 @@ module NotesActions ...@@ -56,7 +56,7 @@ module NotesActions
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note) @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note) if @note.is_a?(Note)
Notes::RenderService.new(current_user).execute([@note], @project) Notes::RenderService.new(current_user).execute([@note])
end end
respond_to do |format| respond_to do |format|
......
...@@ -4,7 +4,7 @@ module RendersNotes ...@@ -4,7 +4,7 @@ module RendersNotes
preload_noteable_for_regular_notes(notes) preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project) preload_max_access_for_authors(notes, @project)
preload_first_time_contribution_for_authors(noteable, notes) preload_first_time_contribution_for_authors(noteable, notes)
Notes::RenderService.new(current_user).execute(notes, @project) Notes::RenderService.new(current_user).execute(notes)
notes notes
end end
......
...@@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController ...@@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController
.new(@projects, offset: params[:offset].to_i, filter: event_filter) .new(@projects, offset: params[:offset].to_i, filter: event_filter)
.to_a .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 end
def user_actions def user_actions
......
class Projects::ApplicationController < ApplicationController class Projects::ApplicationController < ApplicationController
include RoutableActions include RoutableActions
include ChecksCollaboration
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
before_action :project before_action :project
...@@ -31,14 +32,6 @@ class Projects::ApplicationController < ApplicationController ...@@ -31,14 +32,6 @@ class Projects::ApplicationController < ApplicationController
@repository ||= project.repository @repository ||= project.repository
end 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) def authorize_action!(action)
unless can?(current_user, action, project) unless can?(current_user, action, project)
return access_denied! return access_denied!
...@@ -91,9 +84,4 @@ class Projects::ApplicationController < ApplicationController ...@@ -91,9 +84,4 @@ class Projects::ApplicationController < ApplicationController
def check_issues_available! def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user) return render_404 unless @project.feature_available?(:issues, current_user)
end end
def user_access(project)
@user_access ||= {}
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
end
end end
...@@ -34,6 +34,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -34,6 +34,7 @@ class Projects::CommitController < Projects::ApplicationController
def pipelines def pipelines
@pipelines = @commit.pipelines.order(id: :desc) @pipelines = @commit.pipelines.order(id: :desc)
@pipelines = @pipelines.where(ref: params[:ref]) if params[:ref]
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_update_issuable!, only: [:edit, :update, :move] before_action :authorize_update_issuable!, only: [:edit, :update, :move]
# Allow create a new branch and empty WIP merge request from current issue # 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 respond_to :html
......
...@@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
skip_before_action :merge_request skip_before_action :merge_request
before_action :whitelist_query_limiting, only: [:create] 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 :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create] before_action :build_merge_request, except: [:create]
......
...@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController
private private
def render_json_with_notes_serializer 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) render json: note_serializer.represent(note)
end end
......
...@@ -159,7 +159,10 @@ class IssuableFinder ...@@ -159,7 +159,10 @@ class IssuableFinder
finder_options = { include_subgroups: params[:include_subgroups], only_owned: true } finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute
else 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 end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) @projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
...@@ -316,9 +319,9 @@ class IssuableFinder ...@@ -316,9 +319,9 @@ class IssuableFinder
def by_project(items) def by_project(items)
items = items =
if project? if project?
items.of_projects(projects(items)).references_project items.of_projects(projects).references_project
elsif projects(items) elsif projects
items.merge(projects(items).reorder(nil)).join_project items.merge(projects.reorder(nil)).join_project
else else
items.none items.none
end end
......
...@@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder ...@@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder
if @source_project.fork_network if @source_project.fork_network
@source_project.fork_network.projects @source_project.fork_network.projects
.public_or_visible_to_user(current_user) .public_or_visible_to_user(current_user)
.non_archived
.with_feature_available_for_user(:merge_requests, current_user) .with_feature_available_for_user(:merge_requests, current_user)
else else
Project.where(id: source_project) Project.where(id: source_project)
......
...@@ -59,7 +59,7 @@ module BlobHelper ...@@ -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' } 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) elsif can_modify_blob?(blob, project, ref)
button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' 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) edit_fork_button_tag(common_classes, project, label, edit_modify_file_fork_params(action), action)
end end
end end
...@@ -280,7 +280,7 @@ module BlobHelper ...@@ -280,7 +280,7 @@ module BlobHelper
options << link_to("submit an issue", new_project_issue_path(project)) options << link_to("submit an issue", new_project_issue_path(project))
end 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 if merge_project
options << link_to("create a merge request", project_new_merge_request_path(project)) options << link_to("create a merge request", project_new_merge_request_path(project))
end end
...@@ -334,7 +334,7 @@ module BlobHelper ...@@ -334,7 +334,7 @@ module BlobHelper
# Web IDE (Beta) requires the user to have this feature enabled # Web IDE (Beta) requires the user to have this feature enabled
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref)) elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
edit_link_tag(text, edit_path, common_classes) 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)) edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
end end
end end
......
...@@ -94,7 +94,7 @@ module CiStatusHelper ...@@ -94,7 +94,7 @@ module CiStatusHelper
def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left') def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
project = pipeline_status.project 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( render_status_with_link(
'commit', 'commit',
...@@ -105,7 +105,7 @@ module CiStatusHelper ...@@ -105,7 +105,7 @@ module CiStatusHelper
def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left') def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project project = commit.project
path = pipelines_project_commit_path(project, commit) path = pipelines_project_commit_path(project, commit, ref: ref)
render_status_with_link( render_status_with_link(
'commit', 'commit',
......
...@@ -163,7 +163,7 @@ module CommitsHelper ...@@ -163,7 +163,7 @@ module CommitsHelper
tooltip = "#{action.capitalize} this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip 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? 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}" 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) elsif can?(current_user, :fork_project, @project)
continue_params = { continue_params = {
......
...@@ -3,7 +3,7 @@ module CompareHelper ...@@ -3,7 +3,7 @@ module CompareHelper
from.present? && from.present? &&
to.present? && to.present? &&
from != to && 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?(from) &&
project.repository.branch_exists?(to) project.repository.branch_exists?(to)
end end
......
...@@ -82,8 +82,8 @@ module IssuesHelper ...@@ -82,8 +82,8 @@ module IssuesHelper
names.to_sentence names.to_sentence
end end
def award_state_class(awards, current_user) def award_state_class(awardable, awards, current_user)
if !current_user if !can?(current_user, :award_emoji, awardable)
"disabled" "disabled"
elsif current_user && awards.find { |a| a.user_id == current_user.id } elsif current_user && awards.find { |a| a.user_id == current_user.id }
"active" "active"
...@@ -126,6 +126,17 @@ module IssuesHelper ...@@ -126,6 +126,17 @@ module IssuesHelper
link_to link_text, path link_to link_text, path
end 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 # Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue module_function :url_for_issue
module_function :url_for_internal_issue module_function :url_for_internal_issue
......
...@@ -256,7 +256,7 @@ module MarkupHelper ...@@ -256,7 +256,7 @@ module MarkupHelper
return '' unless html.present? return '' unless html.present?
context.merge!( context.merge!(
current_user: (current_user if defined?(current_user)), current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter # RelativeLinkFilter
commit: @commit, commit: @commit,
......
...@@ -138,6 +138,18 @@ module MergeRequestsHelper ...@@ -138,6 +138,18 @@ module MergeRequestsHelper
end end
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) def merge_params_ee(merge_request)
{} {}
end end
......
...@@ -6,10 +6,6 @@ module NotesHelper ...@@ -6,10 +6,6 @@ module NotesHelper
end end
end end
def note_editable?(note)
Ability.can_edit_note?(current_user, note)
end
def note_supports_quick_actions?(note) def note_supports_quick_actions?(note)
Notes::QuickActionsService.supported?(note) Notes::QuickActionsService.supported?(note)
end end
......
...@@ -157,40 +157,6 @@ module ProjectsHelper ...@@ -157,40 +157,6 @@ module ProjectsHelper
current_user&.recent_push(@project) current_user&.recent_push(@project)
end 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 def link_to_autodeploy_doc
link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank' link_to _('About auto deploy'), help_page_path('ci/autodeploy/index'), target: '_blank'
end end
...@@ -274,16 +240,6 @@ module ProjectsHelper ...@@ -274,16 +240,6 @@ module ProjectsHelper
private 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) def get_project_nav_tabs(project, current_user)
nav_tabs = [:home] nav_tabs = [:home]
...@@ -447,14 +403,6 @@ module ProjectsHelper ...@@ -447,14 +403,6 @@ module ProjectsHelper
filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]") filtered_message.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
end 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) def project_child_container_class(view_path)
view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}" view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
end end
...@@ -463,20 +411,6 @@ module ProjectsHelper ...@@ -463,20 +411,6 @@ module ProjectsHelper
IssuesFinder.new(current_user, project_id: project.id).execute IssuesFinder.new(current_user, project_id: project.id).execute
end 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 def restricted_levels
return [] if current_user.admin? return [] if current_user.admin?
......
...@@ -46,10 +46,6 @@ class Ability ...@@ -46,10 +46,6 @@ class Ability
end end
end end
def can_edit_note?(user, note)
allowed?(user, :edit_note, note)
end
def allowed?(user, action, subject = :global, opts = {}) def allowed?(user, action, subject = :global, opts = {})
if subject.is_a?(Hash) if subject.is_a?(Hash)
opts, subject = subject, :global opts, subject = subject, :global
......
...@@ -19,7 +19,7 @@ class BroadcastMessage < ActiveRecord::Base ...@@ -19,7 +19,7 @@ class BroadcastMessage < ActiveRecord::Base
after_commit :flush_redis_cache after_commit :flush_redis_cache
def self.current 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? return messages if messages.empty?
...@@ -36,6 +36,10 @@ class BroadcastMessage < ActiveRecord::Base ...@@ -36,6 +36,10 @@ class BroadcastMessage < ActiveRecord::Base
where('ends_at > :now', now: Time.zone.now).order_id_asc where('ends_at > :now', now: Time.zone.now).order_id_asc
end end
def self.cache_expires_in
nil
end
def active? def active?
started? && !ended? started? && !ended?
end end
......
...@@ -79,11 +79,7 @@ module Awardable ...@@ -79,11 +79,7 @@ module Awardable
end end
def user_can_award?(current_user, name) def user_can_award?(current_user, name)
if user_authored?(current_user) awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self)
!awardable_votes?(normalize_name(name))
else
true
end
end end
def user_authored?(current_user) def user_authored?(current_user)
...@@ -119,4 +115,12 @@ module Awardable ...@@ -119,4 +115,12 @@ module Awardable
def normalize_name(name) def normalize_name(name)
Gitlab::Emoji.normalize_emoji_name(name) Gitlab::Emoji.normalize_emoji_name(name)
end end
def awardable_by_user?(current_user, name)
if user_authored?(current_user)
!awardable_votes?(normalize_name(name))
else
true
end
end
end end
...@@ -34,7 +34,7 @@ class DeployToken < ActiveRecord::Base ...@@ -34,7 +34,7 @@ class DeployToken < ActiveRecord::Base
end end
def has_access_to?(requested_project) def has_access_to?(requested_project)
project == requested_project active? && project == requested_project
end end
# This is temporal. Currently we limit DeployToken # This is temporal. Currently we limit DeployToken
......
...@@ -110,7 +110,10 @@ class Event < ActiveRecord::Base ...@@ -110,7 +110,10 @@ class Event < ActiveRecord::Base
end end
end end
# Remove this method when removing Gitlab.rails5? code.
def subclass_from_attributes(attrs) def subclass_from_attributes(attrs)
return super if Gitlab.rails5?
# Without this Rails will keep calling this method on the returned class, # Without this Rails will keep calling this method on the returned class,
# resulting in an infinite loop. # resulting in an infinite loop.
return unless self == Event return unless self == Event
......
...@@ -11,7 +11,7 @@ module Ci ...@@ -11,7 +11,7 @@ module Ci
end end
condition(:owner_of_job) do condition(:owner_of_job) do
can?(:developer_access) && @subject.triggered_by?(@user) @subject.triggered_by?(@user)
end end
rule { protected_ref }.policy do rule { protected_ref }.policy do
...@@ -19,6 +19,6 @@ module Ci ...@@ -19,6 +19,6 @@ module Ci
prevent :erase_build prevent :erase_build
end 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
end end
...@@ -7,23 +7,17 @@ module Ci ...@@ -7,23 +7,17 @@ module Ci
end end
condition(:owner_of_schedule) do condition(:owner_of_schedule) do
can?(:developer_access) && pipeline_schedule.owned_by?(@user) pipeline_schedule.owned_by?(@user)
end end
condition(:non_owner_of_schedule) do rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
!pipeline_schedule.owned_by?(@user)
end
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 :update_pipeline_schedule
enable :admin_pipeline_schedule enable :admin_pipeline_schedule
end 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 enable :take_ownership_pipeline_schedule
end end
......
...@@ -18,9 +18,7 @@ class IssuablePolicy < BasePolicy ...@@ -18,9 +18,7 @@ class IssuablePolicy < BasePolicy
rule { locked & ~is_project_member }.policy do rule { locked & ~is_project_member }.policy do
prevent :create_note prevent :create_note
prevent :update_note
prevent :admin_note prevent :admin_note
prevent :resolve_note prevent :resolve_note
prevent :edit_note
end end
end end
class NotePolicy < BasePolicy class NotePolicy < BasePolicy
delegate { @subject.project } 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(: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(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
condition(:editable, scope: :subject) { @subject.editable? } condition(:editable, scope: :subject) { @subject.editable? }
rule { ~editable | anonymous }.prevent :edit_note rule { ~editable }.prevent :admin_note
rule { is_author | admin }.enable :edit_note
rule { can?(:master_access) }.enable :edit_note
rule { is_author }.policy do rule { is_author }.policy do
enable :read_note enable :read_note
enable :update_note
enable :admin_note enable :admin_note
enable :resolve_note enable :resolve_note
end end
rule { for_merge_request & is_noteable_author }.policy do rule { is_noteable_author }.policy do
enable :resolve_note enable :resolve_note
end end
end end
...@@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy ...@@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy
end end
rule { anonymous }.prevent :comment_personal_snippet rule { anonymous }.prevent :comment_personal_snippet
rule { can?(:comment_personal_snippet) }.enable :award_emoji
end end
class ProjectPolicy < BasePolicy class ProjectPolicy < BasePolicy
def self.create_read_update_admin(name) extend ClassMethods
[
:"create_#{name}", READONLY_FEATURES_WHEN_ARCHIVED = %i[
:"read_#{name}", issue
:"update_#{name}", list
:"admin_#{name}" merge_request
] label
end 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" desc "User is a project owner"
condition :owner do condition :owner do
...@@ -15,7 +29,7 @@ class ProjectPolicy < BasePolicy ...@@ -15,7 +29,7 @@ class ProjectPolicy < BasePolicy
end end
desc "Project has public builds enabled" 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 # For guest access we use #team_member? so we can use
# project.members, which gets cached in subject scope. # project.members, which gets cached in subject scope.
...@@ -35,7 +49,7 @@ class ProjectPolicy < BasePolicy ...@@ -35,7 +49,7 @@ class ProjectPolicy < BasePolicy
condition(:master) { team_access_level >= Gitlab::Access::MASTER } condition(:master) { team_access_level >= Gitlab::Access::MASTER }
desc "Project is public" 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" desc "Project is visible to internal users"
condition(:internal_access) do condition(:internal_access) do
...@@ -46,7 +60,7 @@ class ProjectPolicy < BasePolicy ...@@ -46,7 +60,7 @@ class ProjectPolicy < BasePolicy
condition(:group_member, scope: :subject) { project_group_member? } condition(:group_member, scope: :subject) { project_group_member? }
desc "Project is archived" 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? } condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
...@@ -56,10 +70,10 @@ class ProjectPolicy < BasePolicy ...@@ -56,10 +70,10 @@ class ProjectPolicy < BasePolicy
end end
desc "Project has an external wiki" 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" 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" desc "Has merge requests allowing pushes to user"
condition(:has_merge_requests_allowing_pushes, scope: :subject) do condition(:has_merge_requests_allowing_pushes, scope: :subject) do
...@@ -126,6 +140,7 @@ class ProjectPolicy < BasePolicy ...@@ -126,6 +140,7 @@ class ProjectPolicy < BasePolicy
rule { can?(:guest_access) }.policy do rule { can?(:guest_access) }.policy do
enable :read_project enable :read_project
enable :create_merge_request_in
enable :read_board enable :read_board
enable :read_list enable :read_list
enable :read_wiki enable :read_wiki
...@@ -140,6 +155,7 @@ class ProjectPolicy < BasePolicy ...@@ -140,6 +155,7 @@ class ProjectPolicy < BasePolicy
enable :create_note enable :create_note
enable :upload_file enable :upload_file
enable :read_cycle_analytics enable :read_cycle_analytics
enable :award_emoji
end end
# These abilities are not allowed to admins that are not members of the project, # These abilities are not allowed to admins that are not members of the project,
...@@ -197,7 +213,7 @@ class ProjectPolicy < BasePolicy ...@@ -197,7 +213,7 @@ class ProjectPolicy < BasePolicy
enable :create_pipeline enable :create_pipeline
enable :update_pipeline enable :update_pipeline
enable :create_pipeline_schedule enable :create_pipeline_schedule
enable :create_merge_request enable :create_merge_request_from
enable :create_wiki enable :create_wiki
enable :push_code enable :push_code
enable :resolve_note enable :resolve_note
...@@ -208,7 +224,7 @@ class ProjectPolicy < BasePolicy ...@@ -208,7 +224,7 @@ class ProjectPolicy < BasePolicy
end end
rule { can?(:master_access) }.policy do rule { can?(:master_access) }.policy do
enable :delete_protected_branch enable :push_to_delete_protected_branch
enable :update_project_snippet enable :update_project_snippet
enable :update_environment enable :update_environment
enable :update_deployment enable :update_deployment
...@@ -231,37 +247,50 @@ class ProjectPolicy < BasePolicy ...@@ -231,37 +247,50 @@ class ProjectPolicy < BasePolicy
end end
rule { archived }.policy do rule { archived }.policy do
prevent :create_merge_request
prevent :push_code prevent :push_code
prevent :delete_protected_branch prevent :push_to_delete_protected_branch
prevent :update_merge_request prevent :request_access
prevent :admin_merge_request 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 end
rule { merge_requests_disabled | repository_disabled }.policy do 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 end
rule { issues_disabled & merge_requests_disabled }.policy do rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin(:label)) prevent(*create_read_update_admin_destroy(:label))
prevent(*create_read_update_admin(:milestone)) prevent(*create_read_update_admin_destroy(:milestone))
end end
rule { snippets_disabled }.policy do rule { snippets_disabled }.policy do
prevent(*create_read_update_admin(:project_snippet)) prevent(*create_read_update_admin_destroy(:project_snippet))
end end
rule { wiki_disabled & ~has_external_wiki }.policy do 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) prevent(:download_wiki_code)
end end
rule { builds_disabled | repository_disabled }.policy do rule { builds_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin(:build)) prevent(*create_update_admin_destroy(:pipeline))
prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline])) prevent(*create_read_update_admin_destroy(:build))
prevent(*create_read_update_admin(:pipeline_schedule)) prevent(*create_read_update_admin_destroy(:pipeline_schedule))
prevent(*create_read_update_admin(:environment)) prevent(*create_read_update_admin_destroy(:environment))
prevent(*create_read_update_admin(:deployment)) prevent(*create_read_update_admin_destroy(:deployment))
end end
rule { repository_disabled }.policy do rule { repository_disabled }.policy do
...@@ -272,7 +301,7 @@ class ProjectPolicy < BasePolicy ...@@ -272,7 +301,7 @@ class ProjectPolicy < BasePolicy
end end
rule { container_registry_disabled }.policy do rule { container_registry_disabled }.policy do
prevent(*create_read_update_admin(:container_image)) prevent(*create_read_update_admin_destroy(:container_image))
end end
rule { anonymous & ~public_project }.prevent_all rule { anonymous & ~public_project }.prevent_all
...@@ -314,13 +343,6 @@ class ProjectPolicy < BasePolicy ...@@ -314,13 +343,6 @@ class ProjectPolicy < BasePolicy
enable :read_pipeline_schedule enable :read_pipeline_schedule
end 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 # These rules are included to allow maintainers of projects to push to certain
# to run pipelines for the branches they have access to. # to run pipelines for the branches they have access to.
rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do
......
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 ...@@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
include GitlabRoutingHelper include GitlabRoutingHelper
include MarkupHelper include MarkupHelper
include TreeHelper include TreeHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
presents :merge_request presents :merge_request
...@@ -152,11 +153,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -152,11 +153,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end end
def can_revert_on_current_merge_request? 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 end
def can_cherry_pick_on_current_merge_request? 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 end
def can_push_to_source_branch? def can_push_to_source_branch?
...@@ -195,12 +196,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -195,12 +196,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end.sort.to_sentence end.sort.to_sentence
end 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? def user_can_fork_project?
can?(current_user, :fork_project, project) can?(current_user, :fork_project, project)
end end
......
...@@ -29,6 +29,10 @@ class IssueEntity < IssuableEntity ...@@ -29,6 +29,10 @@ class IssueEntity < IssuableEntity
expose :can_update do |issue| expose :can_update do |issue|
can?(request.current_user, :update_issue, issue) can?(request.current_user, :update_issue, issue)
end end
expose :can_award_emoji do |issue|
can?(request.current_user, :award_emoji, issue)
end
end end
expose :create_note_path do |issue| expose :create_note_path do |issue|
......
...@@ -15,7 +15,11 @@ class NoteEntity < API::Entities::Note ...@@ -15,7 +15,11 @@ class NoteEntity < API::Entities::Note
expose :current_user do expose :current_user do
expose :can_edit do |note| 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
end end
......
...@@ -149,7 +149,8 @@ module Auth ...@@ -149,7 +149,8 @@ module Auth
def deploy_token_can_pull?(requested_project) def deploy_token_can_pull?(requested_project)
has_authentication_ability?(:read_container_image) && has_authentication_ability?(:read_container_image) &&
current_user.is_a?(DeployToken) && current_user.is_a?(DeployToken) &&
current_user.has_access_to?(requested_project) current_user.has_access_to?(requested_project) &&
current_user.read_registry?
end end
## ##
......
...@@ -4,9 +4,6 @@ module Ci ...@@ -4,9 +4,6 @@ module Ci
class RegisterJobService class RegisterJobService
attr_reader :runner 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?) Result = Struct.new(:build, :valid?)
def initialize(runner) def initialize(runner)
...@@ -107,22 +104,10 @@ module Ci ...@@ -107,22 +104,10 @@ module Ci
end end
def register_success(job) def register_success(job)
labels = { shared_runner: runner.shared?, job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
jobs_running_for_project: jobs_running_for_project(job) }
job_queue_duration_seconds.observe(labels, Time.now - job.queued_at)
attempt_counter.increment attempt_counter.increment
end 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 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") @failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
end end
...@@ -132,7 +117,7 @@ module Ci ...@@ -132,7 +117,7 @@ module Ci
end end
def job_queue_duration_seconds 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 end
end end
module Events module Events
class RenderService < BaseRenderer class RenderService < BaseRenderer
def execute(events, atom_request: false) def execute(events, atom_request: false)
events.map(&:note).compact.group_by(&:project).each do |project, notes| notes = events.map(&:note).compact
render_notes(notes, project, atom_request)
end render_notes(notes, atom_request)
end end
private private
def render_notes(notes, project, atom_request) def render_notes(notes, atom_request)
Notes::RenderService.new(current_user).execute(notes, project, render_options(atom_request)) Notes::RenderService
.new(current_user)
.execute(notes, render_options(atom_request))
end end
def render_options(atom_request) def render_options(atom_request)
......
...@@ -71,8 +71,8 @@ module MergeRequests ...@@ -71,8 +71,8 @@ module MergeRequests
params.delete(:source_project_id) params.delete(:source_project_id)
params.delete(:target_project_id) params.delete(:target_project_id)
unless can?(current_user, :read_project, @source_project) && unless can?(current_user, :create_merge_request_from, @source_project) &&
can?(current_user, :read_project, @project) can?(current_user, :create_merge_request_in, @project)
raise Gitlab::Access::AccessDeniedError raise Gitlab::Access::AccessDeniedError
end end
......
...@@ -3,19 +3,18 @@ module Notes ...@@ -3,19 +3,18 @@ module Notes
# Renders a collection of Note instances. # Renders a collection of Note instances.
# #
# notes - The notes to render. # notes - The notes to render.
# project - The project to use for redacting. #
# user - The user viewing the notes.
# Possible options: # Possible options:
#
# requested_path - The request path. # requested_path - The request path.
# project_wiki - The project's wiki. # project_wiki - The project's wiki.
# ref - The current Git reference. # ref - The current Git reference.
# only_path - flag to turn relative paths into absolute ones. # only_path - flag to turn relative paths into absolute ones.
# xhtml - flag to save the html in XHTML # xhtml - flag to save the html in XHTML
def execute(notes, project, **opts) def execute(notes, options = {})
renderer = Banzai::ObjectRenderer.new(project, current_user, **opts) Banzai::ObjectRenderer
.new(user: current_user, redaction_context: options)
renderer.render(notes, :note) .render(notes, :note)
end end
end end
end end
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
- if @project.archived? - if @project.archived?
%li %li
%span.light archived: %span.light archived:
%strong repository is read-only %strong project is read-only
%li %li
%span.light access: %span.light access:
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
= link_to 'Block', block_admin_user_path(user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put = link_to 'Block', block_admin_user_path(user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put
- if user.access_locked? - if user.access_locked?
%li %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) - if can?(current_user, :destroy_user, user)
%li.divider %li.divider
- if user.can_be_removed? - if user.can_be_removed?
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } } .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| - awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", %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) } } data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji) = emoji_icon(emoji)
%span.award-control-text.js-counter %span.award-control-text.js-counter
= awards.count = awards.count
- if current_user - if can?(current_user, :award_emoji, awardable)
.award-menu-holder.js-award-holder .award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button', %button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': 'Add reaction', 'aria-label': 'Add reaction',
......
%ul.nav-links.nav-tabs.new-session-tabs.single-tab %ul.nav-links.new-session-tabs.single-tab
%li.active %li.active
%a= tab_title %a= tab_title
%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? - if crowd_enabled?
%li.active %li.active
= link_to "Crowd", "#crowd", 'data-toggle' => 'tab' = link_to "Crowd", "#crowd", 'data-toggle' => 'tab'
......
%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' } %ul.nav-links.new-session-tabs{ role: 'tablist' }
%li.active{ role: 'presentation' } %li.active{ role: 'presentation' }
%a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
- if allow_signup? - if allow_signup?
......
...@@ -19,8 +19,8 @@ ...@@ -19,8 +19,8 @@
%li.dropdown-bold-header GitLab %li.dropdown-bold-header GitLab
- if @project&.persisted? - if @project&.persisted?
- create_project_issue = can?(current_user, :create_issue, @project) - create_project_issue = show_new_issue_link?(@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)
- create_project_snippet = can?(current_user, :create_project_snippet, @project) - create_project_snippet = can?(current_user, :create_project_snippet, @project)
- if create_project_issue || merge_project || create_project_snippet - if create_project_issue || merge_project || create_project_snippet
%li.dropdown-bold-header This project %li.dropdown-bold-header This project
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.nav-icon-container .nav-icon-container
= sprite_icon('project') = sprite_icon('project')
%span.nav-item-name %span.nav-item-name
Overview Project
%ul.sidebar-sub-level-items %ul.sidebar-sub-level-items
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do = nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
.flex-right - if can?(current_user, :create_merge_request_in, event.project.default_merge_request_target)
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do .flex-right
#{ _('Create merge request') } = 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_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 @@ ...@@ -4,7 +4,7 @@
- diverging_commit_counts = @repository.diverging_commit_counts(branch) - diverging_commit_counts = @repository.diverging_commit_counts(branch)
- number_commits_behind = diverging_commit_counts[:behind] - number_commits_behind = diverging_commit_counts[:behind]
- number_commits_ahead = diverging_commit_counts[:ahead] - 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}" } %li{ class: "branch-item js-branch-#{branch.name}" }
.branch-info .branch-info
.branch-title .branch-title
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
title: s_('Branches|The default branch cannot be deleted') } title: s_('Branches|The default branch cannot be deleted') }
= icon("trash-o") = icon("trash-o")
- elsif protected_branch?(@project, branch) - 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", %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
title: s_('Branches|Delete protected branch'), title: s_('Branches|Delete protected branch'),
data: { toggle: "modal", data: { toggle: "modal",
......
- 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 .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...') } %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') }
= icon('plus') = icon('plus')
= icon("caret-down") = icon("caret-down")
%ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown %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 - if can_create_issue || merge_project || can_create_project_snippet
%li.dropdown-header= _('This project') %li.dropdown-header= _('This project')
...@@ -20,17 +24,17 @@ ...@@ -20,17 +24,17 @@
- if can_create_project_snippet - if can_create_project_snippet
%li= link_to _('New snippet'), new_project_snippet_path(@project) %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') %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') %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- unless @project.empty_repo? - unless @project.empty_repo?
%li= link_to _('New branch'), new_project_branch_path(@project) %li= link_to _('New branch'), new_project_branch_path(@project)
%li= link_to _('New tag'), new_project_tag_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') %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'), - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice, notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now } notice_now: edit_in_new_fork_notice_now }
......
...@@ -7,5 +7,6 @@ ...@@ -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') - 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} %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 - if can?(current_user, :create_cluster, @project)
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success' .text-center
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
- can_collaborate = can_collaborate_with_project?(@project)
.page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) } .page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) }
.header-main-content .header-main-content
= render partial: 'signature', object: @commit.signature = render partial: 'signature', object: @commit.signature
...@@ -32,12 +34,13 @@ ...@@ -32,12 +34,13 @@
%li.d-block.d-sm-none.d-md-none %li.d-block.d-sm-none.d-md-none
= link_to project_tree_path(@project, @commit) do = link_to project_tree_path(@project, @commit) do
#{ _('Browse Files') } #{ _('Browse Files') }
- unless @commit.has_been_reverted?(current_user) - if can_collaborate && !@commit.has_been_reverted?(current_user)
%li.clearfix %li.clearfix
= revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false) = revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
%li.clearfix - if can_collaborate
= cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false) %li.clearfix
- if can_collaborate_with_project? = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
- if can?(current_user, :push_code, @project)
%li.clearfix %li.clearfix
= link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit) = link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
%li.divider %li.divider
......
...@@ -17,6 +17,6 @@ ...@@ -17,6 +17,6 @@
.limited-width-notes .limited-width-notes
= render "shared/notes/notes_with_form", :autocomplete => true = 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| - %w(revert cherry-pick).each do |type|
= render "projects/commit/change", type: type, commit: @commit, title: @commit.title = render "projects/commit/change", type: type, commit: @commit, title: @commit.title
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
- link = commit_path(project, commit, merge_request: merge_request) - link = commit_path(project, commit, merge_request: merge_request)
- cache_key = [project.full_path, - cache_key = [project.full_path,
ref,
commit.id, commit.id,
Gitlab::CurrentSettings.current_application_settings, Gitlab::CurrentSettings.current_application_settings,
@path.presence, @path.presence,
...@@ -54,7 +55,7 @@ ...@@ -54,7 +55,7 @@
- if commit.status(ref) - if commit.status(ref)
= render_commit_status(commit, ref: 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 .commit-sha-group
.label.label-monospace .label.label-monospace
......
...@@ -114,17 +114,18 @@ ...@@ -114,17 +114,18 @@
Archive project Archive project
- if @project.archived? - if @project.archived?
%p %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. %strong Once active this project shows up in the search and on the dashboard.
= link_to 'Unarchive project', unarchive_project_path(@project), = 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" method: :post, class: "btn btn-success"
- else - else
%p %p
Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches. Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches.
%strong Archived projects cannot be committed to! %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), = 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" method: :post, class: "btn btn-warning"
.sub-section.rename-respository .sub-section.rename-respository
%h4.warning-title %h4.warning-title
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
= icon('rss') = icon('rss')
- if @can_bulk_update - if @can_bulk_update
= button_tag "Edit issues", class: "btn btn-secondary append-right-10 js-bulk-update-toggle" = button_tag "Edit issues", class: "btn btn-secondary append-right-10 js-bulk-update-toggle"
= link_to "New issue", new_project_issue_path(@project, - if show_new_issue_link?(@project)
issue: { assignee_id: finder.assignee.try(:id), = link_to "New issue", new_project_issue_path(@project,
milestone_id: finder.milestones.first.try(:id) }), issue: { assignee_id: finder.assignee.try(:id),
class: "btn btn-new", milestone_id: finder.milestones.first.try(:id) }),
title: "New issue", class: "btn btn-new",
id: "new_issue_link" title: "New issue",
id: "new_issue_link"
- 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) - 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) - 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_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) - 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 @@ ...@@ -7,6 +7,7 @@
- can_update_issue = can?(current_user, :update_issue, @issue) - can_update_issue = can?(current_user, :update_issue, @issue)
- can_report_spam = @issue.submittable_as_spam_by?(current_user) - can_report_spam = @issue.submittable_as_spam_by?(current_user)
- can_create_issue = show_new_issue_link?(@project)
.detail-page-header .detail-page-header
.detail-page-header-body .detail-page-header-body
...@@ -42,16 +43,18 @@ ...@@ -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' %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 - 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' %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 - if can_create_issue
%li.divider - if can_update_issue || can_report_spam
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link' %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 = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
- if can_report_spam - 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 '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 - if can_create_issue
New 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 .issue-details.issuable-details
.detail-page-description.content-block .detail-page-description.content-block
......
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
- else - else
Job has been erased #{time_ago_with_tooltip(@build.erased_at)} 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 .build-trace-container.prepend-top-default
.top-bar.js-top-bar .top-bar.js-top-bar
.js-truncated-info.truncated-info.d-none.d-sm-block.float-left.hidden< .js-truncated-info.truncated-info.d-none.d-sm-block.float-left.hidden<
......
- @no_container = true - @no_container = true
- @can_bulk_update = can?(current_user, :admin_merge_request, @project) - @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 - new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
- page_title "Merge Requests" - page_title "Merge Requests"
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
%template{ 'v-else' => '' } %template{ 'v-else' => '' }
= render 'shared/icons/icon_resolve_discussion.svg' = render 'shared/icons/icon_resolve_discussion.svg'
- if current_user - if can?(current_user, :award_emoji, note)
- if note.emoji_awardable? - if note.emoji_awardable?
- user_authored = note.user_authored?(current_user) - user_authored = note.user_authored?(current_user)
.note-actions-item .note-actions-item
......
.card.protected-branches-list.js-protected-branches-list .protected-branches-list.js-protected-branches-list
- if @protected_branches.empty? - if @protected_branches.empty?
.card-header .card-header
%h3.card-title %h3.card-title
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment