Merge branch 'master' into bootstrap4
This commit is contained in:
commit
b8401cd0b2
|
@ -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:
|
||||||
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: []
|
||||||
|
|
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -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)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.94.0
|
0.95.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
4.0.0
|
4.1.0
|
||||||
|
|
|
@ -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
|
||||||
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
|
||||||
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 @@
|
||||||
},
|
},
|
||||||
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 {
|
||||||
...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 {
|
||||||
<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 {
|
||||||
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.`;
|
||||||
<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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
</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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
return title;
|
return title;
|
||||||
},
|
},
|
||||||
handleAward(awardName) {
|
handleAward(awardName) {
|
||||||
if (!this.isLoggedIn) {
|
if (!this.canAwardEmoji) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
: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.`;
|
||||||
: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 {
|
||||||
: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"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
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();
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
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();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,152 +0,0 @@
|
||||||
/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/
|
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import VisibilitySelect from '../../../visibility_select';
|
|
||||||
|
|
||||||
function highlightChanges($elm) {
|
|
||||||
$elm.addClass('highlight-changes');
|
|
||||||
setTimeout(() => $elm.removeClass('highlight-changes'), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ProjectNew {
|
|
||||||
constructor() {
|
|
||||||
this.toggleSettings = this.toggleSettings.bind(this);
|
|
||||||
this.$selects = $('.features select');
|
|
||||||
this.$repoSelects = this.$selects.filter('.js-repo-select');
|
|
||||||
this.$projectSelects = this.$selects.not('.js-repo-select');
|
|
||||||
|
|
||||||
$('.project-edit-container').on('ajax:before', () => {
|
|
||||||
$('.project-edit-container').hide();
|
|
||||||
return $('.save-project-loader').show();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.initVisibilitySelect();
|
|
||||||
|
|
||||||
this.toggleSettings();
|
|
||||||
this.toggleSettingsOnclick();
|
|
||||||
this.toggleRepoVisibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
initVisibilitySelect() {
|
|
||||||
const visibilityContainer = document.querySelector('.js-visibility-select');
|
|
||||||
if (!visibilityContainer) return;
|
|
||||||
const visibilitySelect = new VisibilitySelect(visibilityContainer);
|
|
||||||
visibilitySelect.init();
|
|
||||||
|
|
||||||
const $visibilitySelect = $(visibilityContainer).find('select');
|
|
||||||
let projectVisibility = $visibilitySelect.val();
|
|
||||||
const PROJECT_VISIBILITY_PRIVATE = '0';
|
|
||||||
|
|
||||||
$visibilitySelect.on('change', () => {
|
|
||||||
const newProjectVisibility = $visibilitySelect.val();
|
|
||||||
|
|
||||||
if (projectVisibility !== newProjectVisibility) {
|
|
||||||
this.$projectSelects.each((idx, select) => {
|
|
||||||
const $select = $(select);
|
|
||||||
const $options = $select.find('option');
|
|
||||||
const values = $.map($options, e => e.value);
|
|
||||||
|
|
||||||
// if switched to "private", limit visibility options
|
|
||||||
if (newProjectVisibility === PROJECT_VISIBILITY_PRIVATE) {
|
|
||||||
if ($select.val() !== values[0] && $select.val() !== values[1]) {
|
|
||||||
$select.val(values[1]).trigger('change');
|
|
||||||
highlightChanges($select);
|
|
||||||
}
|
|
||||||
$options.slice(2).disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if switched from "private", increase visibility for non-disabled options
|
|
||||||
if (projectVisibility === PROJECT_VISIBILITY_PRIVATE) {
|
|
||||||
$options.enable();
|
|
||||||
if ($select.val() !== values[0] && $select.val() !== values[values.length - 1]) {
|
|
||||||
$select.val(values[values.length - 1]).trigger('change');
|
|
||||||
highlightChanges($select);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
projectVisibility = newProjectVisibility;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSettings() {
|
|
||||||
this.$selects.each(function () {
|
|
||||||
var $select = $(this);
|
|
||||||
var className = $select.data('field')
|
|
||||||
.replace(/_/g, '-')
|
|
||||||
.replace('access-level', 'feature');
|
|
||||||
ProjectNew._showOrHide($select, '.' + className);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSettingsOnclick() {
|
|
||||||
this.$selects.on('change', this.toggleSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
static _showOrHide(checkElement, container) {
|
|
||||||
const $container = $(container);
|
|
||||||
|
|
||||||
if ($(checkElement).val() !== '0') {
|
|
||||||
return $container.show();
|
|
||||||
}
|
|
||||||
return $container.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleRepoVisibility() {
|
|
||||||
var $repoAccessLevel = $('.js-repo-access-level select');
|
|
||||||
var $lfsEnabledOption = $('.js-lfs-enabled select');
|
|
||||||
var containerRegistry = document.querySelectorAll('.js-container-registry')[0];
|
|
||||||
var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
|
|
||||||
var prevSelectedVal = parseInt($repoAccessLevel.val(), 10);
|
|
||||||
|
|
||||||
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
|
|
||||||
.nextAll()
|
|
||||||
.hide();
|
|
||||||
|
|
||||||
$repoAccessLevel
|
|
||||||
.off('change')
|
|
||||||
.on('change', function () {
|
|
||||||
var selectedVal = parseInt($repoAccessLevel.val(), 10);
|
|
||||||
|
|
||||||
this.$repoSelects.each(function () {
|
|
||||||
var $this = $(this);
|
|
||||||
var repoSelectVal = parseInt($this.val(), 10);
|
|
||||||
|
|
||||||
$this.find('option').enable();
|
|
||||||
|
|
||||||
if (selectedVal < repoSelectVal || repoSelectVal === prevSelectedVal) {
|
|
||||||
$this.val(selectedVal).trigger('change');
|
|
||||||
highlightChanges($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this.find("option[value='" + selectedVal + "']").nextAll().disable();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selectedVal) {
|
|
||||||
this.$repoSelects.removeClass('disabled');
|
|
||||||
|
|
||||||
if ($lfsEnabledOption.length) {
|
|
||||||
$lfsEnabledOption.removeClass('disabled');
|
|
||||||
highlightChanges($lfsEnabledOption);
|
|
||||||
}
|
|
||||||
if (containerRegistry) {
|
|
||||||
containerRegistry.style.display = '';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.$repoSelects.addClass('disabled');
|
|
||||||
|
|
||||||
if ($lfsEnabledOption.length) {
|
|
||||||
$lfsEnabledOption.val('false').addClass('disabled');
|
|
||||||
highlightChanges($lfsEnabledOption);
|
|
||||||
}
|
|
||||||
if (containerRegistry) {
|
|
||||||
containerRegistry.style.display = 'none';
|
|
||||||
containerRegistryCheckbox.checked = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevSelectedVal = selectedVal;
|
|
||||||
}.bind(this));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
export default function initProjectLoadingSpinner() {
|
||||||
|
const $formContainer = $('.project-edit-container');
|
||||||
|
const $loadingSpinner = $('.save-project-loader');
|
||||||
|
|
||||||
|
// show loading spinner when saving
|
||||||
|
$formContainer.on('ajax:before', () => {
|
||||||
|
$formContainer.hide();
|
||||||
|
$loadingSpinner.show();
|
||||||
|
});
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
|
||||||
* Does that setting the current selected tab in the localStorage
|
* 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 @@
|
||||||
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 @@
|
||||||
NavigationTabs,
|
NavigationTabs,
|
||||||
NavigationControls,
|
NavigationControls,
|
||||||
},
|
},
|
||||||
mixins: [
|
mixins: [pipelinesMixin, CIPaginationMixin],
|
||||||
pipelinesMixin,
|
|
||||||
CIPaginationMixin,
|
|
||||||
],
|
|
||||||
props: {
|
props: {
|
||||||
store: {
|
store: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -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 @@
|
||||||
},
|
},
|
||||||
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 @@
|
||||||
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 @@
|
||||||
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 @@
|
||||||
* 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 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
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 @@
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,27 @@
|
||||||
/* 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 {
|
||||||
* @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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
export default {
|
|
||||||
name: 'time-tracking-estimate-only-pane',
|
|
||||||
props: {
|
|
||||||
timeEstimateHumanReadable: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
template: `
|
|
||||||
<div class="time-tracking-estimate-only-pane">
|
|
||||||
<span class="bold">
|
|
||||||
{{ s__('TimeTracking|Estimated:') }}
|
|
||||||
</span>
|
|
||||||
{{ timeEstimateHumanReadable }}
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
};
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'TimeTrackingEstimateOnlyPane',
|
||||||
|
props: {
|
||||||
|
timeEstimateHumanReadable: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="time-tracking-estimate-only-pane">
|
||||||
|
<span class="bold">
|
||||||
|
{{ s__('TimeTracking|Estimated:') }}
|
||||||
|
</span>
|
||||||
|
{{ timeEstimateHumanReadable }}
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -1,7 +1,8 @@
|
||||||
|
<script>
|
||||||
import { sprintf, s__ } from '../../../locale';
|
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 {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
template: `
|
|
||||||
<div class="time-tracking-help-state">
|
|
||||||
<div class="time-tracking-info">
|
|
||||||
<h4>
|
|
||||||
{{ __('Track time with quick actions') }}
|
|
||||||
</h4>
|
|
||||||
<p>
|
|
||||||
{{ __('Quick actions can be used in the issues description and comment boxes.') }}
|
|
||||||
</p>
|
|
||||||
<p v-html="estimateText">
|
|
||||||
</p>
|
|
||||||
<p v-html="spendText">
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
class="btn btn-default learn-more-button"
|
|
||||||
:href="href"
|
|
||||||
>
|
|
||||||
{{ __('Learn more') }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
};
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="time-tracking-help-state">
|
||||||
|
<div class="time-tracking-info">
|
||||||
|
<h4>
|
||||||
|
{{ __('Track time with quick actions') }}
|
||||||
|
</h4>
|
||||||
|
<p>
|
||||||
|
{{ __('Quick actions can be used in the issues description and comment boxes.') }}
|
||||||
|
</p>
|
||||||
|
<p v-html="estimateText">
|
||||||
|
</p>
|
||||||
|
<p v-html="spendText">
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
class="btn btn-default learn-more-button"
|
||||||
|
:href="href"
|
||||||
|
>
|
||||||
|
{{ __('Learn more') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -1,9 +1,9 @@
|
||||||
<script>
|
<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 {
|
||||||
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 {
|
||||||
statusIcon,
|
statusIcon,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
mr: { type: Object, required: true },
|
mr: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -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 @@
|
||||||
$(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 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.discussion-notes {
|
.discussion-notes {
|
||||||
padding: 0 $gl-padding $gl-padding;
|
|
||||||
min-height: 35px;
|
min-height: 35px;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
|
|
@ -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 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 @@
|
||||||
.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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.note-discussion {
|
||||||
|
.timeline-entry-inner {
|
||||||
|
padding: $gl-padding 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.editing-spinner {
|
.editing-spinner {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
||||||
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 @@
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 3px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stage-column {
|
.stage-column {
|
||||||
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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
|
||||||
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
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
module ChecksCollaboration
|
||||||
|
def can_collaborate_with_project?(project, ref: nil)
|
||||||
|
return true if can?(current_user, :push_code, project)
|
||||||
|
|
||||||
|
can_create_merge_request =
|
||||||
|
can?(current_user, :create_merge_request_in, project) &&
|
||||||
|
current_user.already_forked?(project)
|
||||||
|
|
||||||
|
can_create_merge_request ||
|
||||||
|
user_access(project).can_push_to_branch?(ref)
|
||||||
|
end
|
||||||
|
|
||||||
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||||
|
# enabling this so we can easily cache the user access value as it might be
|
||||||
|
# used across multiple calls in the view
|
||||||
|
def user_access(project)
|
||||||
|
@user_access ||= {}
|
||||||
|
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
|
||||||
|
end
|
||||||
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||||
|
end
|
|
@ -41,7 +41,7 @@ module NotesActions
|
||||||
@note = Notes::CreateService.new(note_project, current_user, create_params).execute
|
@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
|
||||||
@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
|
||||||
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
|
||||||
.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
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
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
|
||||||
@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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
# 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
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
|
||||||
enable :play_pipeline_schedule
|
|
||||||
end
|
|
||||||
|
|
||||||
rule { can?(:master_access) | 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
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -1,26 +1,21 @@
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { anonymous }.prevent :comment_personal_snippet
|
rule { anonymous }.prevent :comment_personal_snippet
|
||||||
|
|
||||||
|
rule { can?(:comment_personal_snippet) }.enable :award_emoji
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
class ProjectPolicy
|
||||||
|
module ClassMethods
|
||||||
|
def create_read_update_admin_destroy(name)
|
||||||
|
[
|
||||||
|
:"read_#{name}",
|
||||||
|
*create_update_admin_destroy(name)
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_update_admin_destroy(name)
|
||||||
|
[
|
||||||
|
:"create_#{name}",
|
||||||
|
:"update_#{name}",
|
||||||
|
:"admin_#{name}",
|
||||||
|
:"destroy_#{name}"
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
|
||||||
include GitlabRoutingHelper
|
include 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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
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
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
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
|
||||||
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
|
||||||
# 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 @@
|
||||||
- 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 @@
|
||||||
= 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 @@
|
||||||
.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',
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
%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
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
%ul.new-session-tabs.nav-links.nav-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) }
|
%ul.nav-links.new-session-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) }
|
||||||
- if crowd_enabled?
|
- if crowd_enabled?
|
||||||
%li.active
|
%li.active
|
||||||
= link_to "Crowd", "#crowd", 'data-toggle' => 'tab'
|
= link_to "Crowd", "#crowd", 'data-toggle' => 'tab'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' }
|
%ul.nav-links.new-session-tabs{ role: 'tablist' }
|
||||||
%li.active{ role: 'presentation' }
|
%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 @@
|
||||||
%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 @@
|
||||||
.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 @@
|
||||||
|
|
||||||
#{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') }
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
- if can_change_visibility_level?(@project, current_user)
|
|
||||||
.select-wrapper
|
|
||||||
= form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select select-control')
|
|
||||||
= icon('chevron-down')
|
|
||||||
- else
|
|
||||||
.info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } }
|
|
||||||
= visibility_level_icon(@project.visibility_level)
|
|
||||||
%strong
|
|
||||||
= visibility_level_label(@project.visibility_level)
|
|
|
@ -4,7 +4,7 @@
|
||||||
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
|
- 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 @@
|
||||||
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",
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
- if current_user
|
- can_create_issue = show_new_issue_link?(@project)
|
||||||
|
- can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
|
||||||
|
- can_push_code = can?(current_user, :push_code, @project)
|
||||||
|
- create_mr_from_new_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
|
||||||
|
- merge_project = merge_request_source_project_for_project(@project)
|
||||||
|
|
||||||
|
- show_menu = can_create_issue || can_create_project_snippet || can_push_code || create_mr_from_new_fork || merge_project
|
||||||
|
|
||||||
|
- if show_menu
|
||||||
.project-action-button.dropdown.inline
|
.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 @@
|
||||||
- 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 @@
|
||||||
- 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'
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
- can_collaborate = can_collaborate_with_project?(@project)
|
||||||
|
|
||||||
.page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) }
|
.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 @@
|
||||||
%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 @@
|
||||||
|
|
||||||
.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 @@
|
||||||
|
|
||||||
- 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 @@
|
||||||
- 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 @@
|
||||||
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 @@
|
||||||
= 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"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
- can_create_merge_request = can?(current_user, :create_merge_request, @project)
|
|
||||||
- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
|
|
||||||
- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
|
|
||||||
|
|
||||||
- if can?(current_user, :push_code, @project)
|
- 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 @@
|
||||||
|
|
||||||
- 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 @@
|
||||||
%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 @@
|
||||||
- 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<
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
- @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 @@
|
||||||
%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
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue