Commit edd7b91d authored by Marcus Amargi's avatar Marcus Amargi

Merge branch 'master' into issue-description-field-typo

parents 2c15aacc ad1ce123
<svg width="24" height="30" viewBox="0 0 24 30" xmlns="http://www.w3.org/2000/svg"><title>cursor</title><g fill="none" fill-rule="evenodd"><path d="M24 12.105c0 6.686-5.74 11.58-12 17.895C5.74 23.684 0 18.79 0 12.105 0 5.42 5.373 0 12 0s12 5.42 12 12.105z" fill="#1F78D1" fill-rule="nonzero"/><path d="M15.28 25.249c1.458-1.475 2.539-2.635 3.474-3.747 2.851-3.394 4.203-6.265 4.203-9.397 0-6.111-4.908-11.062-10.957-11.062-6.05 0-10.957 4.951-10.957 11.062 0 3.132 1.352 6.003 4.203 9.397.935 1.112 2.016 2.272 3.474 3.747.511.517 2.216 2.213 3.28 3.275 1.064-1.062 2.769-2.758 3.28-3.275z" fill="#FFF"/><path d="M14.551 8.256A6.874 6.874 0 0 0 12 7.787c-.91 0-1.763.156-2.558.469-.79.308-1.42.725-1.888 1.252-.465.527-.697 1.096-.697 1.708 0 .5.159.977.476 1.433.321.45.772.841 1.352 1.172l.583.334-.181.643c-.107.407-.263.79-.469 1.152a6.604 6.604 0 0 0 1.842-1.145l.288-.254.381.04c.309.035.599.053.871.053.91 0 1.761-.154 2.551-.462.795-.312 1.424-.732 1.889-1.259.468-.526.703-1.096.703-1.707 0-.612-.235-1.181-.703-1.708-.465-.527-1.094-.944-1.889-1.252zm2.645.81c.536.656.804 1.373.804 2.15 0 .776-.268 1.495-.804 2.156-.535.656-1.263 1.176-2.183 1.56-.92.38-1.924.57-3.013.57a9.16 9.16 0 0 1-.971-.054 7.32 7.32 0 0 1-3.08 1.62 5.044 5.044 0 0 1-.764.148h-.033a.26.26 0 0 1-.181-.074.324.324 0 0 1-.107-.18v-.007c-.014-.018-.016-.045-.007-.08.014-.037.018-.059.014-.068 0-.009.01-.031.033-.067a.645.645 0 0 0 .04-.06 1.73 1.73 0 0 0 .047-.054l.054-.06a53.034 53.034 0 0 1 .435-.489c.049-.049.118-.136.207-.26.094-.126.168-.24.221-.342.054-.103.114-.235.181-.395.067-.161.125-.33.174-.51-.7-.397-1.254-.888-1.66-1.473A3.261 3.261 0 0 1 6 11.216c0-.777.268-1.494.804-2.15.535-.66 1.263-1.18 2.183-1.56.92-.384 1.924-.576 3.013-.576 1.09 0 2.094.192 3.013.576.92.38 1.648.9 2.183 1.56z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
<svg width="48" height="60" viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg"><title>cursor_2x</title><g fill="none" fill-rule="evenodd"><path d="M48 24.21C48 37.583 36.522 47.369 24 60 11.478 47.368 0 37.582 0 24.21 0 10.84 10.745 0 24 0s24 10.84 24 24.21z" fill="#1F78D1" fill-rule="nonzero"/><path d="M30.56 50.497c2.915-2.95 5.078-5.268 6.947-7.493 5.703-6.788 8.406-12.53 8.406-18.793 0-12.223-9.815-22.124-21.913-22.124S2.087 11.988 2.087 24.211c0 6.263 2.703 12.005 8.406 18.793 1.87 2.225 4.032 4.544 6.947 7.493 1.022 1.035 4.432 4.426 6.56 6.55 2.128-2.124 5.538-5.515 6.56-6.55z" fill="#FFF"/><path d="M29.103 16.512c-1.58-.625-3.282-.938-5.103-.938-1.821 0-3.527.313-5.116.938-1.58.616-2.84 1.45-3.777 2.504-.928 1.054-1.393 2.192-1.393 3.415 0 1 .317 1.956.951 2.866.643.902 1.545 1.684 2.706 2.344l1.165.67-.362 1.286a9.603 9.603 0 0 1-.937 2.303 13.208 13.208 0 0 0 3.683-2.29l.576-.509.763.08c.616.072 1.196.108 1.741.108 1.821 0 3.522-.308 5.103-.925 1.589-.625 2.848-1.464 3.776-2.517.938-1.054 1.407-2.192 1.407-3.416 0-1.223-.469-2.361-1.407-3.415-.928-1.053-2.187-1.888-3.776-2.504zm5.29 1.62c1.071 1.313 1.607 2.746 1.607 4.3 0 1.553-.536 2.99-1.607 4.312-1.072 1.312-2.527 2.353-4.366 3.12-1.84.76-3.848 1.139-6.027 1.139a18.32 18.32 0 0 1-1.942-.107c-1.768 1.562-3.821 2.643-6.16 3.24-.438.126-.947.224-1.527.295h-.067a.521.521 0 0 1-.362-.147.649.649 0 0 1-.214-.362v-.013c-.027-.036-.032-.09-.014-.16.027-.072.036-.117.027-.135 0-.017.022-.062.067-.133a1.29 1.29 0 0 0 .08-.121c.01-.009.04-.045.094-.107a106.068 106.068 0 0 1 .522-.59c.215-.232.367-.401.456-.508.098-.099.236-.273.415-.523.188-.25.335-.477.442-.683.107-.205.228-.468.362-.79.134-.321.25-.66.348-1.018-1.402-.794-2.51-1.777-3.322-2.946C12.402 25.025 12 23.77 12 22.43c0-1.553.536-2.986 1.607-4.299 1.072-1.321 2.527-2.361 4.366-3.12 1.84-.768 3.848-1.152 6.027-1.152 2.179 0 4.188.384 6.027 1.152 1.84.759 3.294 1.799 4.366 3.12z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
{"iconCount":180,"spriteSize":82176,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} {"iconCount":181,"spriteSize":81482,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
\ No newline at end of file \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 38 38"><g fill="none" fill-rule="evenodd"><circle cx="19" cy="19" r="18" fill="#FFF"/><path fill="#1F78D1" fill-rule="nonzero" d="M19 38C8.507 38 0 29.493 0 19S8.507 0 19 0s19 8.507 19 19-8.507 19-19 19zm0-2c9.389 0 17-7.611 17-17S28.389 2 19 2 2 9.611 2 19s7.611 17 17 17zm-6.293-8.293c-.63.63-1.707.184-1.707-.707V15a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-7.586l-3.707 3.707zM13 24.586l2.293-2.293A1 1 0 0 1 16 22h8a1 1 0 0 0 1-1v-6a1 1 0 0 0-1-1H14a1 1 0 0 0-1 1v9.586z"/></g></svg>
\ No newline at end of file
...@@ -19,12 +19,9 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) { ...@@ -19,12 +19,9 @@ export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
} }
export function addImageCommentBadge(containerEl, { coordinate, noteId }) { export function addImageCommentBadge(containerEl, { coordinate, noteId }) {
const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge', 'inverted']); const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge']);
const iconEl = document.createElement('i'); buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark');
iconEl.className = 'fa fa-comment-o';
iconEl.setAttribute('aria-label', 'comment');
buttonEl.appendChild(iconEl);
containerEl.appendChild(buttonEl); containerEl.appendChild(buttonEl);
} }
......
...@@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -7,7 +7,7 @@ document.addEventListener('DOMContentLoaded', () => {
const initialDataEl = document.getElementById('js-issuable-app-initial-data'); const initialDataEl = document.getElementById('js-issuable-app-initial-data');
const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"')); const props = JSON.parse(initialDataEl.innerHTML.replace(/&quot;/g, '"'));
$('.issuable-edit').on('click', (e) => { $('.js-issuable-edit').on('click', (e) => {
e.preventDefault(); e.preventDefault();
eventHub.$emit('open.form'); eventHub.$emit('open.form');
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
import issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'issueCommentForm', name: 'commentForm',
data() { data() {
return { return {
note: '', note: '',
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue'; import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue'; import noteAttachment from './note_attachment.vue';
import issueNoteForm from './issue_note_form.vue'; import noteForm from './note_form.vue';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
noteEditedText, noteEditedText,
noteAwardsList, noteAwardsList,
noteAttachment, noteAttachment,
issueNoteForm, noteForm,
}, },
computed: { computed: {
noteBody() { noteBody() {
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
<div <div
v-html="note.note_html" v-html="note.note_html"
class="note-text md"></div> class="note-text md"></div>
<issue-note-form <note-form
v-if="isEditing" v-if="isEditing"
ref="noteForm" ref="noteForm"
@handleFormUpdate="handleFormUpdate" @handleFormUpdate="handleFormUpdate"
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import Flash from '../../flash'; import Flash from '../../flash';
import { SYSTEM_NOTE } from '../constants'; import { SYSTEM_NOTE } from '../constants';
import issueNote from './issue_note.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteableNote from './noteable_note.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue'; import noteSignedOutWidget from './note_signed_out_widget.vue';
import noteEditedText from './note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import issueNoteForm from './issue_note_form.vue'; import noteForm from './note_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
...@@ -25,12 +25,12 @@ ...@@ -25,12 +25,12 @@
}; };
}, },
components: { components: {
issueNote, noteableNote,
userAvatarLink, userAvatarLink,
noteHeader, noteHeader,
noteSignedOutWidget, noteSignedOutWidget,
noteEditedText, noteEditedText,
issueNoteForm, noteForm,
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
}, },
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
return placeholderNote; return placeholderNote;
} }
return issueNote; return noteableNote;
}, },
componentData(note) { componentData(note) {
return note.isPlaceholderNote ? note.notes[0] : note; return note.isPlaceholderNote ? note.notes[0] : note;
...@@ -209,7 +209,7 @@ ...@@ -209,7 +209,7 @@
type="button" type="button"
class="js-vue-discussion-reply btn btn-text-field" class="js-vue-discussion-reply btn btn-text-field"
title="Add a reply">Reply...</button> title="Add a reply">Reply...</button>
<issue-note-form <note-form
v-if="isReplying" v-if="isReplying"
save-button-title="Comment" save-button-title="Comment"
:discussion="note" :discussion="note"
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import noteHeader from './note_header.vue'; import noteHeader from './note_header.vue';
import noteActions from './note_actions.vue'; import noteActions from './note_actions.vue';
import issueNoteBody from './issue_note_body.vue'; import noteBody from './note_body.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
userAvatarLink, userAvatarLink,
noteHeader, noteHeader,
noteActions, noteActions,
issueNoteBody, noteBody,
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
...@@ -123,9 +123,7 @@ ...@@ -123,9 +123,7 @@
// we need to do this to prevent noteForm inconsistent content warning // we need to do this to prevent noteForm inconsistent content warning
// this is something we intentionally do so we need to recover the content // this is something we intentionally do so we need to recover the content
this.note.note = noteText; this.note.note = noteText;
if (this.$refs.noteBody) { this.$refs.noteBody.$refs.noteForm.note = noteText;
this.$refs.noteBody.$refs.noteForm.note = noteText; // TODO: This could be better
}
}, },
}, },
created() { created() {
...@@ -174,7 +172,7 @@ ...@@ -174,7 +172,7 @@
@handleDelete="deleteHandler" @handleDelete="deleteHandler"
/> />
</div> </div>
<issue-note-body <note-body
:note="note" :note="note"
:can-edit="note.current_user.can_edit" :can-edit="note.current_user.can_edit"
:is-editing="isEditing" :is-editing="isEditing"
......
...@@ -4,16 +4,16 @@ ...@@ -4,16 +4,16 @@
import Flash from '../../flash'; import Flash from '../../flash';
import store from '../stores/'; import store from '../stores/';
import * as constants from '../constants'; import * as constants from '../constants';
import issueNote from './issue_note.vue'; import noteableNote from './noteable_note.vue';
import issueDiscussion from './issue_discussion.vue'; import noteableDiscussion from './noteable_discussion.vue';
import systemNote from '../../vue_shared/components/notes/system_note.vue'; import systemNote from '../../vue_shared/components/notes/system_note.vue';
import issueCommentForm from './issue_comment_form.vue'; import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
export default { export default {
name: 'issueNotesApp', name: 'notesApp',
props: { props: {
noteableData: { noteableData: {
type: Object, type: Object,
...@@ -36,10 +36,10 @@ ...@@ -36,10 +36,10 @@
}; };
}, },
components: { components: {
issueNote, noteableNote,
issueDiscussion, noteableDiscussion,
systemNote, systemNote,
issueCommentForm, commentForm,
loadingIcon, loadingIcon,
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
...@@ -69,10 +69,10 @@ ...@@ -69,10 +69,10 @@
} }
return placeholderNote; return placeholderNote;
} else if (note.individual_note) { } else if (note.individual_note) {
return note.notes[0].system ? systemNote : issueNote; return note.notes[0].system ? systemNote : noteableNote;
} }
return issueDiscussion; return noteableDiscussion;
}, },
getComponentData(note) { getComponentData(note) {
return note.individual_note ? note.notes[0] : note; return note.individual_note ? note.notes[0] : note;
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
.then(() => this.checkLocationHash()) .then(() => this.checkLocationHash())
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
Flash('Something went wrong while fetching issue comments. Please try again.'); Flash('Something went wrong while fetching comments. Please try again.');
}); });
}, },
initPolling() { initPolling() {
...@@ -147,6 +147,6 @@ ...@@ -147,6 +147,6 @@
/> />
</ul> </ul>
<issue-comment-form /> <comment-form />
</div> </div>
</template> </template>
import Vue from 'vue'; import Vue from 'vue';
import issueNotesApp from './components/issue_notes_app.vue'; import notesApp from './components/notes_app.vue';
document.addEventListener('DOMContentLoaded', () => new Vue({ document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#js-vue-notes', el: '#js-vue-notes',
components: { components: {
issueNotesApp, notesApp,
}, },
data() { data() {
const notesDataset = document.getElementById('js-vue-notes').dataset; const notesDataset = document.getElementById('js-vue-notes').dataset;
...@@ -32,7 +32,7 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ...@@ -32,7 +32,7 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
}; };
}, },
render(createElement) { render(createElement) {
return createElement('issue-notes-app', { return createElement('notes-app', {
props: { props: {
noteableData: this.noteableData, noteableData: this.noteableData,
notesData: this.notesData, notesData: this.notesData,
......
...@@ -59,8 +59,26 @@ ...@@ -59,8 +59,26 @@
}, },
computed: { computed: {
status() {
return this.job && this.job.status ? this.job.status : {};
},
tooltipText() { tooltipText() {
return `${this.job.name} - ${this.job.status.label}`; const textBuilder = [];
if (this.job.name) {
textBuilder.push(this.job.name);
}
if (this.job.name && this.status.label) {
textBuilder.push('-');
}
if (this.status.label) {
textBuilder.push(`${this.job.status.label}`);
}
return textBuilder.join(' ');
}, },
/** /**
...@@ -78,8 +96,8 @@ ...@@ -78,8 +96,8 @@
<div class="ci-job-component"> <div class="ci-job-component">
<a <a
v-tooltip v-tooltip
v-if="job.status.has_details" v-if="status.has_details"
:href="job.status.details_path" :href="status.details_path"
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body" data-container="body"
...@@ -95,6 +113,7 @@ ...@@ -95,6 +113,7 @@
<div <div
v-else v-else
v-tooltip v-tooltip
class="js-job-component-tooltip"
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body" data-container="body"
...@@ -108,18 +127,18 @@ ...@@ -108,18 +127,18 @@
<action-component <action-component
v-if="hasAction && !isDropdown" v-if="hasAction && !isDropdown"
:tooltip-text="job.status.action.title" :tooltip-text="status.action.title"
:link="job.status.action.path" :link="status.action.path"
:action-icon="job.status.action.icon" :action-icon="status.action.icon"
:action-method="job.status.action.method" :action-method="status.action.method"
/> />
<dropdown-action-component <dropdown-action-component
v-if="hasAction && isDropdown" v-if="hasAction && isDropdown"
:tooltip-text="job.status.action.title" :tooltip-text="status.action.title"
:link="job.status.action.path" :link="status.action.path"
:action-icon="job.status.action.icon" :action-icon="status.action.icon"
:action-method="job.status.action.method" :action-method="status.action.method"
/> />
</div> </div>
</template> </template>
...@@ -11,7 +11,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation { ...@@ -11,7 +11,7 @@ export default class ShortcutsIssuable extends ShortcutsNavigation {
super(); super();
this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form'); this.$replyField = isMergeRequest ? $('.js-main-target-form #note_note') : $('.js-main-target-form .js-vue-comment-form');
this.editBtn = document.querySelector('.issuable-edit'); this.editBtn = document.querySelector('.js-issuable-edit');
Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee')); Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee'));
Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone')); Mousetrap.bind('m', () => ShortcutsIssuable.openSidebarDropdown('milestone'));
......
import Flash from '../../../flash'; import Flash from '../../../flash';
import AssigneeTitle from './assignee_title'; import AssigneeTitle from './assignee_title';
import Assignees from './assignees'; import Assignees from './assignees';
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
export default { export default {
name: 'SidebarAssignees', name: 'SidebarAssignees',
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
loading: false, loading: false,
field: '',
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
field: {
type: String,
required: true,
},
signedIn: {
type: Boolean,
required: false,
default: false,
},
},
components: { components: {
'assignee-title': AssigneeTitle, 'assignee-title': AssigneeTitle,
assignees: Assignees, assignees: Assignees,
...@@ -61,10 +71,6 @@ export default { ...@@ -61,10 +71,6 @@ export default {
eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees); eventHub.$off('sidebar.removeAllAssignees', this.removeAllAssignees);
eventHub.$off('sidebar.saveAssignees', this.saveAssignees); eventHub.$off('sidebar.saveAssignees', this.saveAssignees);
}, },
beforeMount() {
this.field = this.$el.dataset.field;
this.signedIn = typeof this.$el.dataset.signedIn !== 'undefined';
},
template: ` template: `
<div> <div>
<assignee-title <assignee-title
......
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import participants from './participants.vue'; import participants from './participants.vue';
export default { export default {
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
},
components: { components: {
participants, participants,
}, },
...@@ -21,6 +25,7 @@ export default { ...@@ -21,6 +25,7 @@ export default {
<participants <participants
:loading="store.isFetching.participants" :loading="store.isFetching.participants"
:participants="store.participants" :participants="store.participants"
:number-of-less-participants="7" /> :number-of-less-participants="7"
/>
</div> </div>
</template> </template>
<script> <script>
import Store from '../../stores/sidebar_store'; import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import Flash from '../../../flash'; import Flash from '../../../flash';
import { __ } from '../../../locale'; import { __ } from '../../../locale';
...@@ -9,11 +8,15 @@ import subscriptions from './subscriptions.vue'; ...@@ -9,11 +8,15 @@ import subscriptions from './subscriptions.vue';
export default { export default {
data() { data() {
return { return {
mediator: new Mediator(),
store: new Store(), store: new Store(),
}; };
}, },
props: {
mediator: {
type: Object,
required: true,
},
},
components: { components: {
subscriptions, subscriptions,
}, },
......
...@@ -10,6 +10,27 @@ import Translate from '../vue_shared/translate'; ...@@ -10,6 +10,27 @@ import Translate from '../vue_shared/translate';
Vue.use(Translate); Vue.use(Translate);
function mountAssigneesComponent(mediator) {
const el = document.getElementById('js-vue-sidebar-assignees');
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
SidebarAssignees,
},
render: createElement => createElement('sidebar-assignees', {
props: {
mediator,
field: el.dataset.field,
signedIn: el.hasAttribute('data-signed-in'),
},
}),
});
}
function mountConfidentialComponent(mediator) { function mountConfidentialComponent(mediator) {
const el = document.getElementById('js-confidential-entry-point'); const el = document.getElementById('js-confidential-entry-point');
...@@ -49,9 +70,10 @@ function mountLockComponent(mediator) { ...@@ -49,9 +70,10 @@ function mountLockComponent(mediator) {
}).$mount(el); }).$mount(el);
} }
function mountParticipantsComponent() { function mountParticipantsComponent(mediator) {
const el = document.querySelector('.js-sidebar-participants-entry-point'); const el = document.querySelector('.js-sidebar-participants-entry-point');
// eslint-disable-next-line no-new
if (!el) return; if (!el) return;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
...@@ -60,11 +82,15 @@ function mountParticipantsComponent() { ...@@ -60,11 +82,15 @@ function mountParticipantsComponent() {
components: { components: {
sidebarParticipants, sidebarParticipants,
}, },
render: createElement => createElement('sidebar-participants', {}), render: createElement => createElement('sidebar-participants', {
props: {
mediator,
},
}),
}); });
} }
function mountSubscriptionsComponent() { function mountSubscriptionsComponent(mediator) {
const el = document.querySelector('.js-sidebar-subscriptions-entry-point'); const el = document.querySelector('.js-sidebar-subscriptions-entry-point');
if (!el) return; if (!el) return;
...@@ -75,22 +101,35 @@ function mountSubscriptionsComponent() { ...@@ -75,22 +101,35 @@ function mountSubscriptionsComponent() {
components: { components: {
sidebarSubscriptions, sidebarSubscriptions,
}, },
render: createElement => createElement('sidebar-subscriptions', {}), render: createElement => createElement('sidebar-subscriptions', {
props: {
mediator,
},
}),
}); });
} }
function mount(mediator) { function mountTimeTrackingComponent() {
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees'); const el = document.getElementById('issuable-time-tracker');
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page
if (sidebarAssigneesEl) {
new Vue(SidebarAssignees).$mount(sidebarAssigneesEl);
}
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
SidebarTimeTracking,
},
render: createElement => createElement('sidebar-time-tracking', {}),
});
}
export function mountSidebar(mediator) {
mountAssigneesComponent(mediator);
mountConfidentialComponent(mediator); mountConfidentialComponent(mediator);
mountLockComponent(mediator); mountLockComponent(mediator);
mountParticipantsComponent(); mountParticipantsComponent(mediator);
mountSubscriptionsComponent(); mountSubscriptionsComponent(mediator);
new SidebarMoveIssue( new SidebarMoveIssue(
mediator, mediator,
...@@ -98,7 +137,9 @@ function mount(mediator) { ...@@ -98,7 +137,9 @@ function mount(mediator) {
$('.js-move-issue-confirmation-button'), $('.js-move-issue-confirmation-button'),
).init(); ).init();
new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker'); mountTimeTrackingComponent();
} }
export default mount; export function getSidebarOptions() {
return JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
}
import Mediator from './sidebar_mediator'; import Mediator from './sidebar_mediator';
import mountSidebar from './mount_sidebar'; import { mountSidebar, getSidebarOptions } from './mount_sidebar';
function domContentLoaded() { function domContentLoaded() {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); const mediator = new Mediator(getSidebarOptions());
const mediator = new Mediator(sidebarOptions);
mediator.fetch(); mediator.fetch();
mountSidebar(mediator); mountSidebar(mediator);
......
...@@ -8,7 +8,6 @@ export default class SidebarMediator { ...@@ -8,7 +8,6 @@ export default class SidebarMediator {
if (!SidebarMediator.singleton) { if (!SidebarMediator.singleton) {
this.initSingleton(options); this.initSingleton(options);
} }
return SidebarMediator.singleton; return SidebarMediator.singleton;
} }
......
export default class SidebarStore { export default class SidebarStore {
constructor(store) { constructor(options) {
if (!SidebarStore.singleton) { if (!SidebarStore.singleton) {
const { currentUser, rootPath, editable } = store; this.initSingleton(options);
this.currentUser = currentUser;
this.rootPath = rootPath;
this.editable = editable;
this.timeEstimate = 0;
this.totalTimeSpent = 0;
this.humanTimeEstimate = '';
this.humanTimeSpent = '';
this.assignees = [];
this.isFetching = {
assignees: true,
participants: true,
subscriptions: true,
};
this.isLoading = {};
this.autocompleteProjects = [];
this.moveToProjectId = 0;
this.isLockDialogOpen = false;
this.participants = [];
this.subscribed = null;
SidebarStore.singleton = this;
} }
return SidebarStore.singleton; return SidebarStore.singleton;
} }
initSingleton(options) {
const { currentUser, rootPath, editable } = options;
this.currentUser = currentUser;
this.rootPath = rootPath;
this.editable = editable;
this.timeEstimate = 0;
this.totalTimeSpent = 0;
this.humanTimeEstimate = '';
this.humanTimeSpent = '';
this.assignees = [];
this.isFetching = {
assignees: true,
participants: true,
subscriptions: true,
};
this.isLoading = {};
this.autocompleteProjects = [];
this.moveToProjectId = 0;
this.isLockDialogOpen = false;
this.participants = [];
this.subscribed = null;
SidebarStore.singleton = this;
}
setAssigneeData(data) { setAssigneeData(data) {
this.isFetching.assignees = false; this.isFetching.assignees = false;
if (data.assignees) { if (data.assignees) {
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
padding: 1px 5px; padding: 1px 5px;
font-size: 12px; font-size: 12px;
color: $blue-500; color: $blue-500;
width: 23px; width: 24px;
height: 23px; height: 24px;
border: 1px solid $blue-500; border: 1px solid $blue-500;
&:hover, &:hover,
......
...@@ -143,20 +143,48 @@ ...@@ -143,20 +143,48 @@
} }
} }
@mixin dropdown-item-hover {
background-color: $dropdown-item-hover-bg;
color: $gl-text-color;
outline: 0;
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
}
.avatar {
border-color: $white-light;
}
}
@mixin dropdown-link { @mixin dropdown-link {
background: transparent;
border: 0;
border-radius: 0;
box-shadow: none;
display: block; display: block;
font-weight: $gl-font-weight-normal;
position: relative; position: relative;
padding: 5px 8px; padding: 8px 16px;
color: $gl-text-color; color: $gl-text-color;
line-height: initial; line-height: normal;
border-radius: 2px; white-space: normal;
white-space: nowrap;
overflow: hidden; overflow: hidden;
text-align: left;
width: 100%;
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
}
&:hover, &:hover,
&:active,
&:focus, &:focus,
&.is-focused { &.is-focused {
background-color: $dropdown-link-hover-bg; @include dropdown-item-hover;
text-decoration: none; text-decoration: none;
.badge { .badge {
...@@ -166,6 +194,13 @@ ...@@ -166,6 +194,13 @@
&.dropdown-menu-user-link { &.dropdown-menu-user-link {
line-height: 16px; line-height: 16px;
padding-top: 10px;
padding-bottom: 7px;
white-space: nowrap;
.dropdown-menu-user-username {
display: block;
}
} }
.icon-play { .icon-play {
...@@ -187,8 +222,8 @@ ...@@ -187,8 +222,8 @@
z-index: 300; z-index: 300;
min-width: 240px; min-width: 240px;
max-width: 500px; max-width: 500px;
margin-top: 2px; margin-top: $dropdown-vertical-offset;
margin-bottom: 2px; margin-bottom: 24px;
font-size: 14px; font-size: 14px;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
padding: 8px 0; padding: 8px 0;
...@@ -197,6 +232,10 @@ ...@@ -197,6 +232,10 @@
border-radius: $border-radius-base; border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color; box-shadow: 0 2px 4px $dropdown-shadow-color;
&.dropdown-open-top {
margin-bottom: $dropdown-vertical-offset;
}
&.dropdown-open-left { &.dropdown-open-left {
right: 0; right: 0;
left: auto; left: auto;
...@@ -227,16 +266,27 @@ ...@@ -227,16 +266,27 @@
} }
li { li {
display: block;
text-align: left; text-align: left;
list-style: none; list-style: none;
padding: 0 10px; padding: 0 1px;
a,
button,
.menu-item {
@include dropdown-link;
}
} }
.divider { .divider {
height: 1px; height: 1px;
margin: 6px 10px; margin: 6px 0;
padding: 0; padding: 0;
background-color: $dropdown-divider-color; background-color: $dropdown-divider-color;
&:hover {
background-color: $dropdown-divider-color;
}
} }
.separator { .separator {
...@@ -247,10 +297,6 @@ ...@@ -247,10 +297,6 @@
background-color: $dropdown-divider-color; background-color: $dropdown-divider-color;
} }
a {
@include dropdown-link;
}
.dropdown-menu-empty-item a { .dropdown-menu-empty-item a {
&:hover, &:hover,
&:focus { &:focus {
...@@ -262,7 +308,7 @@ ...@@ -262,7 +308,7 @@
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
font-size: 13px; font-size: 13px;
line-height: 22px; line-height: 22px;
padding: 0 16px; padding: 8px 16px;
} }
&.capitalize-header .dropdown-header { &.capitalize-header .dropdown-header {
...@@ -277,7 +323,7 @@ ...@@ -277,7 +323,7 @@
.separator + .dropdown-header, .separator + .dropdown-header,
.separator + .dropdown-bold-header { .separator + .dropdown-bold-header {
padding-top: 2px; padding-top: 10px;
} }
.unclickable { .unclickable {
...@@ -298,48 +344,28 @@ ...@@ -298,48 +344,28 @@
} }
.dropdown-menu li { .dropdown-menu li {
padding: $gl-btn-padding;
cursor: pointer; cursor: pointer;
&.droplab-item-active button {
@include dropdown-item-hover;
}
> a, > a,
> button { > button {
display: flex; display: flex;
margin: 0; margin: 0;
padding: 0;
border-radius: 0;
text-overflow: inherit; text-overflow: inherit;
background-color: inherit;
color: inherit;
border: inherit;
text-align: left; text-align: left;
&:hover,
&:focus {
background-color: inherit;
color: inherit;
}
&.btn .fa:not(:last-child) { &.btn .fa:not(:last-child) {
margin-left: 5px; margin-left: 5px;
} }
} }
&:hover,
&:focus {
background-color: $dropdown-hover-color;
color: $white-light;
}
&.droplab-item-selected i { &.droplab-item-selected i {
visibility: visible; visibility: visible;
} }
&.divider {
margin: 0 8px;
padding: 0;
border-top: $gray-darkest;
}
.icon { .icon {
visibility: hidden; visibility: hidden;
} }
...@@ -431,11 +457,6 @@ ...@@ -431,11 +457,6 @@
} }
} }
.dropdown-menu-user-link {
padding-top: 10px;
padding-bottom: 7px;
}
.dropdown-menu-user-full-name { .dropdown-menu-user-full-name {
display: block; display: block;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
...@@ -464,41 +485,44 @@ ...@@ -464,41 +485,44 @@
.dropdown-menu-align-right { .dropdown-menu-align-right {
left: auto; left: auto;
right: 0; right: 0;
margin-top: -5px;
} }
.dropdown-menu-selectable { .dropdown-menu-selectable {
a { li {
padding-left: 26px; a {
position: relative; padding: 8px 40px;
position: relative;
&.is-indeterminate,
&.is-active {
color: $gl-text-color;
&::before {
position: absolute;
left: 16px;
top: 16px;
transform: translateY(-50%);
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
&.is-indeterminate, &.dropdown-menu-user-link {
&.is-active { &::before {
font-weight: $gl-font-weight-bold; top: 50%;
color: $gl-text-color; }
}
&::before {
position: absolute;
left: 6px;
top: 50%;
transform: translateY(-50%);
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
}
&.is-indeterminate::before { &.is-indeterminate::before {
content: "\f068"; content: "\f068";
} }
&.is-active::before { &.is-active::before {
content: "\f00c"; content: "\f00c";
position: absolute; }
top: 50%;
transform: translateY(-50%);
} }
} }
} }
...@@ -735,136 +759,6 @@ ...@@ -735,136 +759,6 @@
} }
} }
@mixin dropdown-item-hover {
background-color: $dropdown-item-hover-bg;
color: $gl-text-color;
}
// TODO: change global style and remove mixin
@mixin new-style-dropdown($selector: '') {
#{$selector}.dropdown-menu,
#{$selector}.dropdown-menu-nav {
margin-bottom: 24px;
&.dropdown-open-top {
margin-bottom: $dropdown-vertical-offset;
}
li {
display: block;
padding: 0 1px;
&:hover {
background-color: transparent;
}
&.divider {
margin: 6px 0;
&:hover {
background-color: $dropdown-divider-color;
}
}
&.dropdown-header {
padding: 8px 16px;
}
&.droplab-item-active button {
@include dropdown-item-hover;
}
a,
button,
.menu-item {
margin-bottom: 0;
border-radius: 0;
box-shadow: none;
padding: 8px 16px;
text-align: left;
white-space: normal;
width: 100%;
font-weight: $gl-font-weight-normal;
line-height: normal;
&.dropdown-menu-user-link {
white-space: nowrap;
.dropdown-menu-user-username {
display: block;
}
}
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
}
&.is-focused,
&:hover,
&:active,
&:focus {
@include dropdown-item-hover;
background-color: $dropdown-item-hover-bg;
color: $gl-text-color;
// make sure the text color is not overriden
&.text-danger {
color: $brand-danger;
}
}
&.is-active {
font-weight: inherit;
&::before {
top: 16px;
}
&.dropdown-menu-user-link::before {
top: 50%;
transform: translateY(-50%);
}
}
}
&.dropdown-menu-empty-item a {
&:hover,
&:focus {
background-color: transparent;
}
}
}
&.dropdown-menu-selectable {
li {
a {
padding: 8px 40px;
&.is-indeterminate::before,
&.is-active::before {
left: 16px;
}
}
}
}
}
#{$selector}.dropdown-menu-align-right {
margin-top: 2px;
}
.open {
#{$selector}.dropdown-menu,
#{$selector}.dropdown-menu-nav {
@media (max-width: $screen-xs-max) {
max-width: 100%;
}
}
}
}
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
.navbar-gitlab { .navbar-gitlab {
li.header-projects, li.header-projects,
...@@ -891,9 +785,6 @@ ...@@ -891,9 +785,6 @@
} }
} }
@include new-style-dropdown('.breadcrumbs-list .dropdown ');
@include new-style-dropdown('.js-namespace-select + ');
header.header-content .dropdown-menu.projects-dropdown-menu { header.header-content .dropdown-menu.projects-dropdown-menu {
padding: 0; padding: 0;
} }
......
...@@ -50,8 +50,6 @@ ...@@ -50,8 +50,6 @@
} }
.filtered-search-wrapper { .filtered-search-wrapper {
@include new-style-dropdown;
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
...@@ -165,16 +163,6 @@ ...@@ -165,16 +163,6 @@
} }
} }
.droplab-dropdown li.filtered-search-token {
padding: 0;
&:hover,
&:focus {
background-color: inherit;
color: inherit;
}
}
.filtered-search-term { .filtered-search-term {
.name { .name {
background-color: inherit; background-color: inherit;
...@@ -336,21 +324,12 @@ ...@@ -336,21 +324,12 @@
.filtered-search-history-dropdown-content { .filtered-search-history-dropdown-content {
max-height: none; max-height: none;
}
.filtered-search-history-dropdown-item,
.filtered-search-history-clear-button {
@include dropdown-link;
overflow: hidden;
width: 100%;
margin: 0.5em 0;
background-color: transparent; .filtered-search-history-dropdown-item,
border: 0; .filtered-search-history-clear-button {
text-align: left; white-space: nowrap;
white-space: nowrap; text-overflow: ellipsis;
text-overflow: ellipsis; }
} }
.filtered-search-history-dropdown-token { .filtered-search-history-dropdown-token {
...@@ -402,24 +381,9 @@ ...@@ -402,24 +381,9 @@
} }
} }
%filter-dropdown-item-btn-hover {
text-decoration: none;
outline: 0;
.avatar {
border-color: $white-light;
}
}
.droplab-dropdown .dropdown-menu .filter-dropdown-item { .droplab-dropdown .dropdown-menu .filter-dropdown-item {
.btn { .btn {
border: 0;
width: 100%;
text-align: left;
padding: 8px 16px;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden;
border-radius: 0;
.fa { .fa {
width: 15px; width: 15px;
...@@ -434,11 +398,6 @@ ...@@ -434,11 +398,6 @@
height: 17px; height: 17px;
top: 0; top: 0;
} }
&:hover,
&:focus {
@extend %filter-dropdown-item-btn-hover;
}
} }
.dropdown-light-content { .dropdown-light-content {
...@@ -459,17 +418,9 @@ ...@@ -459,17 +418,9 @@
word-break: break-all; word-break: break-all;
} }
} }
&.droplab-item-active .btn {
@extend %filter-dropdown-item-btn-hover;
}
} }
.filter-dropdown-loading { .filter-dropdown-loading {
padding: 8px 16px; padding: 8px 16px;
text-align: center; text-align: center;
} }
.issues-details-filters {
@include new-style-dropdown;
}
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
} }
.navbar-gitlab { .navbar-gitlab {
@include new-style-dropdown;
&.navbar-gitlab { &.navbar-gitlab {
padding: 0 16px; padding: 0 16px;
z-index: 1000; z-index: 1000;
......
...@@ -132,8 +132,6 @@ ul.content-list { ...@@ -132,8 +132,6 @@ ul.content-list {
} }
.controls { .controls {
@include new-style-dropdown;
float: right; float: right;
> .control-text { > .control-text {
......
...@@ -86,8 +86,6 @@ ...@@ -86,8 +86,6 @@
} }
.nav-controls { .nav-controls {
@include new-style-dropdown;
display: inline-block; display: inline-block;
float: right; float: right;
text-align: right; text-align: right;
......
...@@ -50,6 +50,11 @@ ...@@ -50,6 +50,11 @@
&:not(.disabled) { &:not(.disabled) {
cursor: pointer; cursor: pointer;
} }
svg {
width: $gl-padding;
height: $gl-padding;
}
} }
} }
...@@ -139,10 +144,6 @@ ...@@ -139,10 +144,6 @@
} }
} }
.issuable-sidebar {
@include new-style-dropdown;
}
.pikaday-container { .pikaday-container {
.pika-single { .pika-single {
margin-top: 2px; margin-top: 2px;
......
...@@ -343,8 +343,6 @@ a > code { ...@@ -343,8 +343,6 @@ a > code {
@extend .ref-name; @extend .ref-name;
} }
@include new-style-dropdown('.git-revision-dropdown');
/** /**
* Apply Markdown typography * Apply Markdown typography
* *
......
...@@ -721,7 +721,7 @@ $issuable-warning-icon-margin: 4px; ...@@ -721,7 +721,7 @@ $issuable-warning-icon-margin: 4px;
Image Commenting cursor Image Commenting cursor
*/ */
$image-comment-cursor-left-offset: 12; $image-comment-cursor-left-offset: 12;
$image-comment-cursor-top-offset: 30; $image-comment-cursor-top-offset: 12;
/* /*
Popup Popup
......
...@@ -323,8 +323,6 @@ ...@@ -323,8 +323,6 @@
} }
.build-dropdown { .build-dropdown {
@include new-style-dropdown;
margin: $gl-padding 0; margin: $gl-padding 0;
padding: 0; padding: 0;
......
...@@ -13,8 +13,6 @@ ...@@ -13,8 +13,6 @@
max-width: 100%; max-width: 100%;
} }
@include new-style-dropdown('.clusters-dropdown ');
.clusters-container { .clusters-container {
.nav-bar-right { .nav-bar-right {
padding: $gl-padding-top $gl-padding; padding: $gl-padding-top $gl-padding;
......
#cycle-analytics { #cycle-analytics {
@include new-style-dropdown;
max-width: 1000px; max-width: 1000px;
margin: 24px auto 0; margin: 24px auto 0;
position: relative; position: relative;
......
...@@ -32,8 +32,6 @@ ...@@ -32,8 +32,6 @@
} }
.detail-page-header-actions { .detail-page-header-actions {
@include new-style-dropdown;
align-self: center; align-self: center;
flex-shrink: 0; flex-shrink: 0;
flex: 0 0 auto; flex: 0 0 auto;
......
...@@ -581,8 +581,6 @@ ...@@ -581,8 +581,6 @@
} }
.commit-stat-summary { .commit-stat-summary {
@include new-style-dropdown;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
margin-left: -$gl-padding; margin-left: -$gl-padding;
padding-left: $gl-padding; padding-left: $gl-padding;
...@@ -732,18 +730,18 @@ ...@@ -732,18 +730,18 @@
.frame.click-to-comment { .frame.click-to-comment {
position: relative; position: relative;
cursor: image-url('icon_image_comment.svg') cursor: image-url('illustrations/image_comment_light_cursor.svg')
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
// Retina cursor // Retina cursor
cursor: -webkit-image-set(image-url('icon_image_comment.svg') 1x, image-url('icon_image_comment@2x.svg') 2x) cursor: -webkit-image-set(image-url('illustrations/image_comment_light_cursor.svg') 1x, image-url('illustrations/image_comment_light_cursor@2x.svg') 2x)
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto; $image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
.comment-indicator { .comment-indicator {
position: absolute; position: absolute;
padding: 0; padding: 0;
width: (2px * $image-comment-cursor-left-offset); width: (2px * $image-comment-cursor-left-offset);
height: (1px * $image-comment-cursor-top-offset); height: (2px * $image-comment-cursor-top-offset);
// center the indicator to match the top left click region // center the indicator to match the top left click region
margin-top: (-1px * $image-comment-cursor-top-offset) + 2; margin-top: (-1px * $image-comment-cursor-top-offset) + 2;
margin-left: (-1px * $image-comment-cursor-left-offset) + 1; margin-left: (-1px * $image-comment-cursor-left-offset) + 1;
...@@ -778,15 +776,20 @@ ...@@ -778,15 +776,20 @@
.frame .badge, .frame .badge,
.frame .image-comment-badge { .frame .image-comment-badge {
// Center align badges on the frame // Center align badges on the frame
transform: translate3d(-50%, -50%, 0); transform: translate(-50%, -50%);
} }
.image-comment-badge { .image-comment-badge {
@include btn-comment-icon;
position: absolute; position: absolute;
width: 24px;
height: 24px;
padding: 0;
background: none;
border: 0;
&.inverted { > svg {
border-color: $white-light; width: 100%;
height: 100%;
} }
} }
......
...@@ -204,8 +204,6 @@ ...@@ -204,8 +204,6 @@
.gitlab-ci-yml-selector, .gitlab-ci-yml-selector,
.dockerfile-selector, .dockerfile-selector,
.template-type-selector { .template-type-selector {
@include new-style-dropdown;
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
font-family: $regular_font; font-family: $regular_font;
......
...@@ -12,8 +12,6 @@ ...@@ -12,8 +12,6 @@
.environments-container { .environments-container {
.ci-table { .ci-table {
@include new-style-dropdown;
.deployment-column { .deployment-column {
> span { > span {
word-break: break-all; word-break: break-all;
......
...@@ -470,7 +470,8 @@ ...@@ -470,7 +470,8 @@
} }
} }
.milestone-title span { .milestone-title span,
.collapse-truncated-title {
@include str-truncated(100%); @include str-truncated(100%);
display: block; display: block;
margin: 0 4px; margin: 0 4px;
...@@ -487,12 +488,6 @@ ...@@ -487,12 +488,6 @@
} }
} }
.dropdown-content {
a:hover {
color: inherit;
}
}
.dropdown-menu-toggle { .dropdown-menu-toggle {
width: 100%; width: 100%;
padding-top: 6px; padding-top: 6px;
...@@ -511,10 +506,6 @@ ...@@ -511,10 +506,6 @@
} }
} }
.sidebar-move-issue-dropdown {
@include new-style-dropdown;
}
.sidebar-move-issue-confirmation-button { .sidebar-move-issue-confirmation-button {
width: 100%; width: 100%;
......
...@@ -142,8 +142,6 @@ ul.related-merge-requests > li { ...@@ -142,8 +142,6 @@ ul.related-merge-requests > li {
} }
.issue-form { .issue-form {
@include new-style-dropdown;
.select2-container { .select2-container {
width: 250px !important; width: 250px !important;
} }
......
...@@ -116,8 +116,6 @@ ...@@ -116,8 +116,6 @@
} }
.manage-labels-list { .manage-labels-list {
@include new-style-dropdown;
> li:not(.empty-message):not(.is-not-draggable) { > li:not(.empty-message):not(.is-not-draggable) {
background-color: $white-light; background-color: $white-light;
cursor: move; cursor: move;
......
...@@ -58,8 +58,6 @@ ...@@ -58,8 +58,6 @@
} }
.member-form-control { .member-form-control {
@include new-style-dropdown;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
padding-bottom: 5px; padding-bottom: 5px;
margin-left: 0; margin-left: 0;
...@@ -73,8 +71,6 @@ ...@@ -73,8 +71,6 @@
} }
.member-search-form { .member-search-form {
@include new-style-dropdown;
position: relative; position: relative;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
......
...@@ -485,8 +485,6 @@ ...@@ -485,8 +485,6 @@
} }
.mr-source-target { .mr-source-target {
@include new-style-dropdown;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
...@@ -608,8 +606,6 @@ ...@@ -608,8 +606,6 @@
} }
.mr-version-controls { .mr-version-controls {
@include new-style-dropdown;
position: relative; position: relative;
background: $gray-light; background: $gray-light;
color: $gl-text-color; color: $gl-text-color;
...@@ -727,7 +723,3 @@ ...@@ -727,7 +723,3 @@
font-size: 16px; font-size: 16px;
} }
} }
.merge-request-form {
@include new-style-dropdown;
}
...@@ -23,8 +23,6 @@ ...@@ -23,8 +23,6 @@
.new-note, .new-note,
.note-edit-form { .note-edit-form {
.note-form-actions { .note-form-actions {
@include new-style-dropdown;
position: relative; position: relative;
margin: $gl-padding 0 0; margin: $gl-padding 0 0;
} }
......
...@@ -490,8 +490,6 @@ ul.notes { ...@@ -490,8 +490,6 @@ ul.notes {
} }
.note-actions { .note-actions {
@include new-style-dropdown;
align-self: flex-start; align-self: flex-start;
flex-shrink: 0; flex-shrink: 0;
display: inline-flex; display: inline-flex;
......
...@@ -14,7 +14,3 @@ ...@@ -14,7 +14,3 @@
font-size: 18px; font-size: 18px;
} }
} }
.notification-form {
@include new-style-dropdown;
}
...@@ -286,8 +286,6 @@ ...@@ -286,8 +286,6 @@
// Pipeline visualization // Pipeline visualization
.pipeline-actions { .pipeline-actions {
@include new-style-dropdown;
border-bottom: 0; border-bottom: 0;
} }
...@@ -703,9 +701,6 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -703,9 +701,6 @@ button.mini-pipeline-graph-dropdown-toggle {
} }
} }
@include new-style-dropdown('.big-pipeline-graph-dropdown-menu');
@include new-style-dropdown('.mini-pipeline-graph-dropdown-menu');
// dropdown content for big and mini pipeline // dropdown content for big and mini pipeline
.big-pipeline-graph-dropdown-menu, .big-pipeline-graph-dropdown-menu,
.mini-pipeline-graph-dropdown-menu { .mini-pipeline-graph-dropdown-menu {
...@@ -804,7 +799,6 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -804,7 +799,6 @@ button.mini-pipeline-graph-dropdown-toggle {
font-weight: normal; font-weight: normal;
line-height: $line-height-base; line-height: $line-height-base;
white-space: nowrap; white-space: nowrap;
border-radius: 3px;
.ci-job-name-component { .ci-job-name-component {
align-items: center; align-items: center;
......
...@@ -323,8 +323,6 @@ ...@@ -323,8 +323,6 @@
} }
.project-repo-buttons { .project-repo-buttons {
@include new-style-dropdown;
.project-action-button .dropdown-menu { .project-action-button .dropdown-menu {
max-height: 250px; max-height: 250px;
overflow-y: auto; overflow-y: auto;
...@@ -898,8 +896,6 @@ pre.light-well { ...@@ -898,8 +896,6 @@ pre.light-well {
.new-protected-branch, .new-protected-branch,
.new-protected-tag { .new-protected-tag {
@include new-style-dropdown;
label { label {
margin-top: 6px; margin-top: 6px;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
...@@ -919,8 +915,6 @@ pre.light-well { ...@@ -919,8 +915,6 @@ pre.light-well {
.protected-branches-list, .protected-branches-list,
.protected-tags-list { .protected-tags-list {
@include new-style-dropdown;
margin-bottom: 30px; margin-bottom: 30px;
.settings-message { .settings-message {
......
...@@ -116,11 +116,6 @@ input[type="checkbox"]:hover { ...@@ -116,11 +116,6 @@ input[type="checkbox"]:hover {
opacity: 0; opacity: 0;
display: block; display: block;
left: -5px; left: -5px;
padding: 0;
ul {
padding: 10px 0;
}
} }
.dropdown-content { .dropdown-content {
...@@ -185,8 +180,6 @@ input[type="checkbox"]:hover { ...@@ -185,8 +180,6 @@ input[type="checkbox"]:hover {
} }
.search-holder { .search-holder {
@include new-style-dropdown;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
......
...@@ -265,7 +265,3 @@ ...@@ -265,7 +265,3 @@
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
} }
} }
.todos-filters {
@include new-style-dropdown;
}
.tree-holder { .tree-holder {
@include new-style-dropdown;
.nav-block { .nav-block {
margin: 10px 0; margin: 10px 0;
......
class Admin::GroupsController < Admin::ApplicationController class Admin::GroupsController < Admin::ApplicationController
include MembersPresentation
before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update] before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update]
def index def index
...@@ -10,8 +12,10 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -10,8 +12,10 @@ class Admin::GroupsController < Admin::ApplicationController
def show def show
@group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id]) @group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id])
@members = @group.members.order("access_level DESC").page(params[:members_page]) @members = present_members(
@requesters = AccessRequestsFinder.new(@group).execute(current_user) @group.members.order("access_level DESC").page(params[:members_page]))
@requesters = present_members(
AccessRequestsFinder.new(@group).execute(current_user))
@projects = @group.projects.with_statistics.page(params[:projects_page]) @projects = @group.projects.with_statistics.page(params[:projects_page])
end end
......
class Admin::ProjectsController < Admin::ApplicationController class Admin::ProjectsController < Admin::ApplicationController
include MembersPresentation
before_action :project, only: [:show, :transfer, :repository_check] before_action :project, only: [:show, :transfer, :repository_check]
before_action :group, only: [:show, :transfer] before_action :group, only: [:show, :transfer]
...@@ -19,11 +21,14 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -19,11 +21,14 @@ class Admin::ProjectsController < Admin::ApplicationController
def show def show
if @group if @group
@group_members = @group.members.order("access_level DESC").page(params[:group_members_page]) @group_members = present_members(
@group.members.order("access_level DESC").page(params[:group_members_page]))
end end
@project_members = @project.members.page(params[:project_members_page]) @project_members = present_members(
@requesters = AccessRequestsFinder.new(@project).execute(current_user) @project.members.page(params[:project_members_page]))
@requesters = present_members(
AccessRequestsFinder.new(@project).execute(current_user))
end end
def transfer def transfer
......
module MembersPresentation
extend ActiveSupport::Concern
def present_members(members)
Gitlab::View::Presenter::Factory.new(
members,
current_user: current_user,
presenter_class: MembersPresenter
).fabricate!
end
end
class Groups::GroupMembersController < Groups::ApplicationController class Groups::GroupMembersController < Groups::ApplicationController
include MembershipActions include MembershipActions
include MembersPresentation
include SortingHelper include SortingHelper
# Authorize # Authorize
...@@ -14,15 +15,17 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -14,15 +15,17 @@ class Groups::GroupMembersController < Groups::ApplicationController
@members = @members.search(params[:search]) if params[:search].present? @members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort(@sort) @members = @members.sort(@sort)
@members = @members.page(params[:page]).per(50) @members = @members.page(params[:page]).per(50)
@members.includes(:user) @members = present_members(@members.includes(:user))
@requesters = AccessRequestsFinder.new(@group).execute(current_user) @requesters = present_members(
AccessRequestsFinder.new(@group).execute(current_user))
@group_member = @group.group_members.new @group_member = @group.group_members.new
end end
def update def update
@group_member = @group.members_and_requesters.find(params[:id]) @group_member = @group.members_and_requesters.find(params[:id])
.present(current_user: current_user)
return render_403 unless can?(current_user, :update_group_member, @group_member) return render_403 unless can?(current_user, :update_group_member, @group_member)
......
...@@ -8,11 +8,8 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -8,11 +8,8 @@ class Projects::ClustersController < Projects::ApplicationController
STATUS_POLLING_INTERVAL = 10_000 STATUS_POLLING_INTERVAL = 10_000
def index def index
@scope = params[:scope] || 'all' clusters = ClustersFinder.new(project, current_user, :all).execute
@clusters = ClustersFinder.new(project, current_user, @scope).execute.page(params[:page]) @clusters = clusters.page(params[:page]).per(20)
@active_count = ClustersFinder.new(project, current_user, :active).execute.count
@inactive_count = ClustersFinder.new(project, current_user, :inactive).execute.count
@all_count = @active_count + @inactive_count
end end
def new def new
......
class Projects::ProjectMembersController < Projects::ApplicationController class Projects::ProjectMembersController < Projects::ApplicationController
include MembershipActions include MembershipActions
include MembersPresentation
include SortingHelper include SortingHelper
# Authorize # Authorize
...@@ -20,13 +21,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController ...@@ -20,13 +21,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
@group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) @group_links = @group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
end end
@project_members = @project_members.sort(@sort).page(params[:page]) @project_members = present_members(@project_members.sort(@sort).page(params[:page]))
@requesters = AccessRequestsFinder.new(@project).execute(current_user) @requesters = present_members(AccessRequestsFinder.new(@project).execute(current_user))
@project_member = @project.project_members.new @project_member = @project.project_members.new
end end
def update def update
@project_member = @project.members_and_requesters.find(params[:id]) @project_member = @project.members_and_requesters.find(params[:id])
.present(current_user: current_user)
return render_403 unless can?(current_user, :update_project_member, @project_member) return render_403 unless can?(current_user, :update_project_member, @project_member)
......
...@@ -118,20 +118,24 @@ module BlobHelper ...@@ -118,20 +118,24 @@ module BlobHelper
icon("#{file_type_icon_class('file', mode, name)} fw") icon("#{file_type_icon_class('file', mode, name)} fw")
end end
def blob_raw_path def blob_raw_url(only_path: false)
if @build && @entry if @build && @entry
raw_project_job_artifacts_path(@project, @build, path: @entry.path) raw_project_job_artifacts_url(@project, @build, path: @entry.path, only_path: only_path)
elsif @snippet elsif @snippet
if @snippet.project_id if @snippet.project_id
raw_project_snippet_path(@project, @snippet) raw_project_snippet_url(@project, @snippet, only_path: only_path)
else else
raw_snippet_path(@snippet) raw_snippet_url(@snippet, only_path: only_path)
end end
elsif @blob elsif @blob
project_raw_path(@project, @id) project_raw_url(@project, @id, only_path: only_path)
end end
end end
def blob_raw_path
blob_raw_url(only_path: true)
end
# SVGs can contain malicious JavaScript; only include whitelisted # SVGs can contain malicious JavaScript; only include whitelisted
# elements and attributes. Note that this whitelist is by no means complete # elements and attributes. Note that this whitelist is by no means complete
# and may omit some elements. # and may omit some elements.
......
...@@ -104,15 +104,23 @@ module DiffHelper ...@@ -104,15 +104,23 @@ module DiffHelper
].join(' ').html_safe ].join(' ').html_safe
end end
def diff_file_blob_raw_path(diff_file) def diff_file_blob_raw_url(diff_file, only_path: false)
project_raw_path(@project, tree_join(diff_file.content_sha, diff_file.file_path)) project_raw_url(@project, tree_join(diff_file.content_sha, diff_file.file_path), only_path: only_path)
end end
def diff_file_old_blob_raw_path(diff_file) def diff_file_old_blob_raw_url(diff_file, only_path: false)
sha = diff_file.old_content_sha sha = diff_file.old_content_sha
return unless sha return unless sha
project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) project_raw_url(@project, tree_join(diff_file.old_content_sha, diff_file.old_path), only_path: only_path)
end
def diff_file_blob_raw_path(diff_file)
diff_file_blob_raw_url(diff_file, only_path: true)
end
def diff_file_old_blob_raw_path(diff_file)
diff_file_old_blob_raw_url(diff_file, only_path: true)
end end
def diff_file_html_data(project, diff_file_path, diff_commit_id) def diff_file_html_data(project, diff_file_path, diff_commit_id)
......
module LabelsHelper module LabelsHelper
include ActionView::Helpers::TagHelper include ActionView::Helpers::TagHelper
def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
return true if label.is_a?(GroupLabel)
return true unless project
project.feature_available?(issuables_type, current_user)
end
# Link to a Label # Link to a Label
# #
# label - Label object to link to # label - Label object to link to
......
module MembersHelper module MembersHelper
# Returns a `<action>_<source>_member` association, e.g.:
# - admin_project_member, update_project_member, destroy_project_member
# - admin_group_member, update_group_member, destroy_group_member
def action_member_permission(action, member)
"#{action}_#{member.type.underscore}".to_sym
end
def remove_member_message(member, user: nil) def remove_member_message(member, user: nil)
user = current_user if defined?(current_user) user = current_user if defined?(current_user)
......
...@@ -85,8 +85,7 @@ module CacheMarkdownField ...@@ -85,8 +85,7 @@ module CacheMarkdownField
def cached_html_up_to_date?(markdown_field) def cached_html_up_to_date?(markdown_field)
html_field = cached_markdown_fields.html_field(markdown_field) html_field = cached_markdown_fields.html_field(markdown_field)
cached = cached_html_for(markdown_field).present? && __send__(markdown_field).present? # rubocop:disable GitlabSecurity/PublicSend return false if cached_html_for(markdown_field).nil? && !__send__(markdown_field).nil? # rubocop:disable GitlabSecurity/PublicSend
return false unless cached
markdown_changed = attribute_changed?(markdown_field) || false markdown_changed = attribute_changed?(markdown_field) || false
html_changed = attribute_changed?(html_field) || false html_changed = attribute_changed?(html_field) || false
......
...@@ -12,7 +12,7 @@ class Issue < ActiveRecord::Base ...@@ -12,7 +12,7 @@ class Issue < ActiveRecord::Base
include ThrottledTouch include ThrottledTouch
include IgnorableColumn include IgnorableColumn
ignore_column :assignee_id ignore_column :assignee_id, :branch_name
DueDateStruct = Struct.new(:title, :name).freeze DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
......
...@@ -4,6 +4,7 @@ class Member < ActiveRecord::Base ...@@ -4,6 +4,7 @@ class Member < ActiveRecord::Base
include Importable include Importable
include Expirable include Expirable
include Gitlab::Access include Gitlab::Access
include Presentable
attr_accessor :raw_invite_token attr_accessor :raw_invite_token
......
...@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -8,6 +8,7 @@ class MergeRequest < ActiveRecord::Base
include ManualInverseAssociation include ManualInverseAssociation
include EachBatch include EachBatch
include ThrottledTouch include ThrottledTouch
include Gitlab::Utils::StrongMemoize
ignore_column :locked_at, ignore_column :locked_at,
:ref_fetched :ref_fetched
...@@ -53,6 +54,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -53,6 +54,7 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing? after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed after_update :reload_diff_if_branch_changed
after_update :clear_memoized_shas
# When this attribute is true some MR validation is ignored # When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests # It allows us to close or modify broken merge requests
...@@ -387,13 +389,17 @@ class MergeRequest < ActiveRecord::Base ...@@ -387,13 +389,17 @@ class MergeRequest < ActiveRecord::Base
end end
def source_branch_head def source_branch_head
return unless source_project strong_memoize(:source_branch_head) do
if source_project && source_branch_ref
source_project.repository.commit(source_branch_ref) if source_branch_ref source_project.repository.commit(source_branch_ref)
end
end
end end
def target_branch_head def target_branch_head
target_project.repository.commit(target_branch_ref) strong_memoize(:target_branch_head) do
target_project.repository.commit(target_branch_ref)
end
end end
def branch_merge_base_commit def branch_merge_base_commit
...@@ -525,6 +531,13 @@ class MergeRequest < ActiveRecord::Base ...@@ -525,6 +531,13 @@ class MergeRequest < ActiveRecord::Base
end end
end end
def clear_memoized_shas
@target_branch_sha = @source_branch_sha = nil
clear_memoization(:source_branch_head)
clear_memoization(:target_branch_head)
end
def reload_diff_if_branch_changed def reload_diff_if_branch_changed
if (source_branch_changed? || target_branch_changed?) && if (source_branch_changed? || target_branch_changed?) &&
(source_branch_head && target_branch_head) (source_branch_head && target_branch_head)
......
...@@ -104,19 +104,19 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -104,19 +104,19 @@ class MergeRequestDiff < ActiveRecord::Base
def base_commit def base_commit
return unless base_commit_sha return unless base_commit_sha
project.commit(base_commit_sha) project.commit_by(oid: base_commit_sha)
end end
def start_commit def start_commit
return unless start_commit_sha return unless start_commit_sha
project.commit(start_commit_sha) project.commit_by(oid: start_commit_sha)
end end
def head_commit def head_commit
return unless head_commit_sha return unless head_commit_sha
project.commit(head_commit_sha) project.commit_by(oid: head_commit_sha)
end end
def commit_shas def commit_shas
......
...@@ -401,6 +401,9 @@ class Note < ActiveRecord::Base ...@@ -401,6 +401,9 @@ class Note < ActiveRecord::Base
end end
noteable_object&.touch noteable_object&.touch
# We return the noteable object so we can re-use it in EE for ElasticSearch.
noteable_object
end end
def banzai_render_context(field) def banzai_render_context(field)
......
...@@ -659,7 +659,8 @@ class Project < ActiveRecord::Base ...@@ -659,7 +659,8 @@ class Project < ActiveRecord::Base
end end
def import_started? def import_started?
import? && import_status == 'started' # import? does SQL work so only run it if it looks like there's an import running
import_status == 'started' && import?
end end
def import_scheduled? def import_scheduled?
...@@ -1147,7 +1148,7 @@ class Project < ActiveRecord::Base ...@@ -1147,7 +1148,7 @@ class Project < ActiveRecord::Base
def change_head(branch) def change_head(branch)
if repository.branch_exists?(branch) if repository.branch_exists?(branch)
repository.before_change_head repository.before_change_head
repository.write_ref('HEAD', "refs/heads/#{branch}") repository.write_ref('HEAD', "refs/heads/#{branch}", force: true)
repository.copy_gitattributes(branch) repository.copy_gitattributes(branch)
repository.after_change_head repository.after_change_head
reload_default_branch reload_default_branch
......
...@@ -19,6 +19,7 @@ class Repository ...@@ -19,6 +19,7 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository delegate :ref_name_for_sha, to: :raw_repository
delegate :write_ref, to: :raw_repository
CreateTreeError = Class.new(StandardError) CreateTreeError = Class.new(StandardError)
...@@ -237,11 +238,10 @@ class Repository ...@@ -237,11 +238,10 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes) # This will still fail if the file is corrupted (e.g. 0 bytes)
begin begin
write_ref(keep_around_ref_name(sha), sha) write_ref(keep_around_ref_name(sha), sha, force: true)
rescue Rugged::ReferenceError => ex rescue Gitlab::Git::Repository::GitError => ex
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}" # Necessary because https://gitlab.com/gitlab-org/gitlab-ce/issues/20156
rescue Rugged::OSError => ex return true if ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
raise unless ex.message =~ /Failed to create locked file/ && ex.message =~ /File exists/
Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}" Rails.logger.error "Unable to create #{REF_KEEP_AROUND} reference for repository #{path}: #{ex}"
end end
...@@ -251,10 +251,6 @@ class Repository ...@@ -251,10 +251,6 @@ class Repository
ref_exists?(keep_around_ref_name(sha)) ref_exists?(keep_around_ref_name(sha))
end end
def write_ref(ref_path, sha)
rugged.references.create(ref_path, sha, force: true)
end
def diverging_commit_counts(branch) def diverging_commit_counts(branch)
root_ref_hash = raw_repository.commit(root_ref).id root_ref_hash = raw_repository.commit(root_ref).id
cache.fetch(:"diverging_commit_counts_#{branch.name}") do cache.fetch(:"diverging_commit_counts_#{branch.name}") do
...@@ -971,8 +967,7 @@ class Repository ...@@ -971,8 +967,7 @@ class Repository
tmp_remote_name = true tmp_remote_name = true
end end
add_remote(remote_name, url) add_remote(remote_name, url, mirror_refmap: refmap)
set_remote_as_mirror(remote_name, refmap: refmap)
fetch_remote(remote_name, forced: forced) fetch_remote(remote_name, forced: forced)
ensure ensure
remove_remote(remote_name) if tmp_remote_name remove_remote(remote_name) if tmp_remote_name
...@@ -995,7 +990,7 @@ class Repository ...@@ -995,7 +990,7 @@ class Repository
end end
def create_ref(ref, ref_path) def create_ref(ref, ref_path)
raw_repository.write_ref(ref_path, ref) write_ref(ref_path, ref)
end end
def ls_files(ref) def ls_files(ref)
......
class GroupMemberPresenter < MemberPresenter
private
def admin_member_permission
:admin_group_member
end
def update_member_permission
:update_group_member
end
def destroy_member_permission
:destroy_group_member
end
end
class MemberPresenter < Gitlab::View::Presenter::Delegated
presents :member
def access_level_roles
member.class.access_level_roles
end
def can_resend_invite?
invite? &&
can?(current_user, admin_member_permission, source)
end
def can_update?
can?(current_user, update_member_permission, member)
end
def can_remove?
can?(current_user, destroy_member_permission, member)
end
def can_approve?
request? && can_update?
end
private
def admin_member_permission
raise NotImplementedError
end
def update_member_permission
raise NotImplementedError
end
def destroy_member_permission
raise NotImplementedError
end
end
class MembersPresenter < Gitlab::View::Presenter::Delegated
include Enumerable
presents :members
def to_ary
to_a
end
def each
members.each do |member|
yield member.present(current_user: current_user)
end
end
end
class ProjectMemberPresenter < MemberPresenter
private
def admin_member_permission
:admin_project_member
end
def update_member_permission
:update_project_member
end
def destroy_member_permission
:destroy_project_member
end
end
...@@ -38,11 +38,15 @@ module Ci ...@@ -38,11 +38,15 @@ module Ci
begin begin
# In case when 2 runners try to assign the same build, second runner will be declined # In case when 2 runners try to assign the same build, second runner will be declined
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method. # with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
build.runner_id = runner.id begin
build.run! build.runner_id = runner.id
register_success(build) build.run!
register_success(build)
return Result.new(build, true)
return Result.new(build, true)
rescue Ci::Build::MissingDependenciesError
build.drop!(:missing_dependency_failure)
end
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
# We are looping to find another build that is not conflicting # We are looping to find another build that is not conflicting
# It also indicates that this build can be picked and passed to runner. # It also indicates that this build can be picked and passed to runner.
...@@ -54,9 +58,6 @@ module Ci ...@@ -54,9 +58,6 @@ module Ci
# we still have to return 409 in the end, # we still have to return 409 in the end,
# to make sure that this is properly handled by runner. # to make sure that this is properly handled by runner.
valid = false valid = false
rescue Ci::Build::MissingDependenciesError
build.drop!(:missing_dependency_failure)
valid = false
end end
end end
......
...@@ -35,8 +35,17 @@ module Members ...@@ -35,8 +35,17 @@ module Members
def can_update_access_requester?(access_requester, opts = {}) def can_update_access_requester?(access_requester, opts = {})
access_requester && ( access_requester && (
opts[:force] || opts[:force] ||
can?(current_user, action_member_permission(:update, access_requester), access_requester) can?(current_user, update_member_permission(access_requester), access_requester)
) )
end end
def update_member_permission(member)
case member
when GroupMember
:update_group_member
when ProjectMember
:update_project_member
end
end
end end
end end
...@@ -36,7 +36,16 @@ module Members ...@@ -36,7 +36,16 @@ module Members
end end
def can_destroy_member?(member) def can_destroy_member?(member)
member && can?(current_user, action_member_permission(:destroy, member), member) member && can?(current_user, destroy_member_permission(member), member)
end
def destroy_member_permission(member)
case member
when GroupMember
:destroy_group_member
when ProjectMember
:destroy_project_member
end
end end
end end
end end
...@@ -3,15 +3,15 @@ ...@@ -3,15 +3,15 @@
Template Template
.template-selector-dropdowns-wrap .template-selector-dropdowns-wrap
.template-type-selector.js-template-type-selector-wrap.hidden .template-type-selector.js-template-type-selector-wrap.hidden
= dropdown_tag("Choose type", options: { toggle_class: 'btn js-template-type-selector', title: "Choose a template type" } ) = dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector', title: "Choose a template type" } )
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden .license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a license template", options: { toggle_class: 'btn js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } ) = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden .gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'btn js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } ) = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'btn js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } ) = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
.dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden .dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'btn js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } ) = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
.template-selectors-undo-menu.hidden .template-selectors-undo-menu.hidden
%span.text-info Template applied %span.text-info Template applied
%button.btn.btn-sm.btn-info Undo %button.btn.btn-sm.btn-info Undo
.file-content.image_file .file-content.image_file
= image_tag(blob_raw_path, alt: viewer.blob.name) -# Uses the full URL rather than the path, to prevent it from getting prefixed with the asset host.
= image_tag(blob_raw_url, alt: viewer.blob.name)
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
.col-sm-10.create-from .col-sm-10.create-from
.dropdown .dropdown
= hidden_field_tag :ref, default_ref = hidden_field_tag :ref, default_ref
= button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide form-control js-branch-select git-revision-dropdown-toggle', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select git-revision-dropdown-toggle', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref .text-left.dropdown-toggle-text= default_ref
= icon('chevron-down') = icon('chevron-down')
= render 'shared/ref_dropdown', dropdown_class: 'wide' = render 'shared/ref_dropdown', dropdown_class: 'wide'
......
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon("angle-left")
.fade-right= icon("angle-right")
%ul.nav-links.scrolling-tabs
%li{ class: ('active' if @scope == 'active') }>
= link_to project_clusters_path(@project, scope: :active), class: "js-active-tab" do
= s_("ClusterIntegration|Active")
%span.badge= @active_count
%li{ class: ('active' if @scope == 'inactive') }>
= link_to project_clusters_path(@project, scope: :inactive), class: "js-inactive-tab" do
= s_("ClusterIntegration|Inactive")
%span.badge= @inactive_count
%li{ class: ('active' if @scope.nil? || @scope == 'all') }>
= link_to project_clusters_path(@project), class: "js-all-tab" do
= s_("ClusterIntegration|All")
%span.badge= @all_count
...@@ -2,8 +2,12 @@ ...@@ -2,8 +2,12 @@
- page_title "Clusters" - page_title "Clusters"
.clusters-container .clusters-container
- if !@clusters.empty? - if @clusters.empty?
= render "tabs" = render "empty_state"
- else
.top-area.adjust
.nav-text
= s_("ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project")
.ci-table.js-clusters-list .ci-table.js-clusters-list
.gl-responsive-table-row.table-row-header{ role: "row" } .gl-responsive-table-row.table-row-header{ role: "row" }
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
...@@ -16,9 +20,3 @@ ...@@ -16,9 +20,3 @@
- @clusters.each do |cluster| - @clusters.each do |cluster|
= render "cluster", cluster: cluster.present(current_user: current_user) = render "cluster", cluster: cluster.present(current_user: current_user)
= paginate @clusters, theme: "gitlab" = paginate @clusters, theme: "gitlab"
- elsif @scope == 'all'
= render "empty_state"
- else
= render "tabs"
.prepend-top-20.text-center
= s_("ClusterIntegration|There are no clusters to show")
- blob = diff_file.blob - blob = diff_file.blob
- old_blob = diff_file.old_blob - old_blob = diff_file.old_blob
- blob_raw_path = diff_file_blob_raw_path(diff_file) - blob_raw_url = diff_file_blob_raw_url(diff_file)
- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) - old_blob_raw_url = diff_file_old_blob_raw_url(diff_file)
- click_to_comment = local_assigns.fetch(:click_to_comment, true) - click_to_comment = local_assigns.fetch(:click_to_comment, true)
- diff_view_data = local_assigns.fetch(:diff_view_data, '') - diff_view_data = local_assigns.fetch(:diff_view_data, '')
- class_name = '' - class_name = ''
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.two-up.view .two-up.view
.wrap .wrap
.frame.deleted .frame.deleted
= image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) = image_tag(old_blob_raw_url, alt: diff_file.old_path, lazy: false)
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(old_blob.size) %span.meta-filesize= number_to_human_size(old_blob.size)
| |
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
%strong H: %strong H:
%span.meta-height %span.meta-height
.wrap .wrap
= render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.new_path }
%p.image-info.hide %p.image-info.hide
%span.meta-filesize= number_to_human_size(blob.size) %span.meta-filesize= number_to_human_size(blob.size)
| |
...@@ -36,9 +36,9 @@ ...@@ -36,9 +36,9 @@
.swipe.view.hide .swipe.view.hide
.swipe-frame .swipe-frame
.frame.deleted .frame.deleted
= image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) = image_tag(old_blob_raw_url, alt: diff_file.old_path, lazy: false)
.swipe-wrap .swipe-wrap
= render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.new_path }
%span.swipe-bar %span.swipe-bar
%span.top-handle %span.top-handle
%span.bottom-handle %span.bottom-handle
...@@ -46,8 +46,8 @@ ...@@ -46,8 +46,8 @@
.onion-skin.view.hide .onion-skin.view.hide
.onion-skin-frame .onion-skin-frame
.frame.deleted .frame.deleted
= image_tag(old_blob_raw_path, alt: diff_file.old_path, lazy: false) = image_tag(old_blob_raw_url, alt: diff_file.old_path, lazy: false)
= render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.new_path } = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "added js-image-frame #{class_name}", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.new_path }
.controls .controls
.transparent .transparent
.drag-track .drag-track
......
- blob = diff_file.blob - blob = diff_file.blob
- old_blob = diff_file.old_blob - old_blob = diff_file.old_blob
- blob_raw_path = diff_file_blob_raw_path(diff_file) - blob_raw_url = diff_file_blob_raw_url(diff_file)
- old_blob_raw_path = diff_file_old_blob_raw_path(diff_file) - old_blob_raw_url = diff_file_old_blob_raw_url(diff_file)
- click_to_comment = local_assigns.fetch(:click_to_comment, true) - click_to_comment = local_assigns.fetch(:click_to_comment, true)
- diff_view_data = local_assigns.fetch(:diff_view_data, '') - diff_view_data = local_assigns.fetch(:diff_view_data, '')
- class_name = '' - class_name = ''
...@@ -12,5 +12,5 @@ ...@@ -12,5 +12,5 @@
.image.js-single-image{ data: diff_view_data } .image.js-single-image{ data: diff_view_data }
.wrap .wrap
- single_class_name = diff_file.deleted_file? ? 'deleted' : 'added' - single_class_name = diff_file.deleted_file? ? 'deleted' : 'added'
= render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name} js-image-frame", position: position, note_type: DiffNote.name, image_path: blob_raw_path, alt: diff_file.file_path } = render partial: "projects/diffs/image_diff_frame", locals: { class_name: "#{single_class_name} #{class_name} js-image-frame", position: position, note_type: DiffNote.name, image_path: blob_raw_url, alt: diff_file.file_path }
%p.image-info= number_to_human_size(blob.size) %p.image-info= number_to_human_size(blob.size)
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg .dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul %ul
- if can_update_issue - if can_update_issue
%li= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'issuable-edit' %li= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'js-issuable-edit'
- unless current_user == @issue.author - unless current_user == @issue.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue)) %li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue))
- if can_update_issue - if can_update_issue
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link' %li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
- if can_update_issue - if can_update_issue
= link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' = link_to 'Edit', edit_project_issue_path(@project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped js-issuable-edit'
= 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
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
.dropdown-menu.dropdown-menu-align-right.hidden-lg .dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul %ul
- if can_update_merge_request - if can_update_merge_request
%li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit' %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
- unless current_user == @merge_request.author - unless current_user == @merge_request.author
%li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request)) %li= link_to 'Report abuse', new_abuse_report_path(user_id: @merge_request.author.id, ref_url: merge_request_url(@merge_request))
- if can_update_merge_request - if can_update_merge_request
...@@ -37,6 +37,6 @@ ...@@ -37,6 +37,6 @@
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' = link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
- if can_update_merge_request - if can_update_merge_request
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped js-issuable-edit"
= render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request
- project = local_assigns.fetch(:project)
- members = local_assigns.fetch(:members)
.panel.panel-default .panel.panel-default
.panel-heading.flex-project-members-panel .panel-heading.flex-project-members-panel
%span.flex-project-title %span.flex-project-title
Members of Members of
%strong %strong= project.name
#{@project.name} %span.badge= members.total_count
%span.badge= @project_members.total_count = form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
= form_tag project_project_members_path(@project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group .form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
......
...@@ -37,5 +37,5 @@ ...@@ -37,5 +37,5 @@
- if @group_links.any? - if @group_links.any?
= render 'projects/project_members/groups', group_links: @group_links = render 'projects/project_members/groups', group_links: @group_links
= render 'projects/project_members/team', members: @project_members = render 'projects/project_members/team', project: @project, members: @project_members
= paginate @project_members, theme: "gitlab" = paginate @project_members, theme: "gitlab"
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
.col-sm-10.create-from .col-sm-10.create-from
.dropdown .dropdown
= hidden_field_tag :ref, default_ref = hidden_field_tag :ref, default_ref
= button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide form-control js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref .text-left.dropdown-toggle-text= default_ref
= render 'shared/ref_dropdown', dropdown_class: 'wide' = render 'shared/ref_dropdown', dropdown_class: 'wide'
.help-block .help-block
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
- status = label_subscription_status(label, @project).inquiry if current_user - status = label_subscription_status(label, @project).inquiry if current_user
- subject = local_assigns[:subject] - subject = local_assigns[:subject]
- toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user - toggle_subscription_path = toggle_subscription_label_path(label, @project) if current_user
- show_label_merge_requests_link = show_label_issuables_link?(label, :merge_requests, project: @project)
- show_label_issues_link = show_label_issuables_link?(label, :issues, project: @project)
%li{ id: label_css_id, data: { id: label.id } } %li{ id: label_css_id, data: { id: label.id } }
= render "shared/label_row", label: label = render "shared/label_row", label: label
...@@ -12,12 +14,14 @@ ...@@ -12,12 +14,14 @@
= icon('caret-down') = icon('caret-down')
.dropdown-menu.dropdown-menu-align-right .dropdown-menu.dropdown-menu-align-right
%ul %ul
%li - if show_label_merge_requests_link
= link_to_label(label, subject: subject, type: :merge_request) do %li
View merge requests = link_to_label(label, subject: subject, type: :merge_request) do
%li View merge requests
= link_to_label(label, subject: subject) do - if show_label_issues_link
View open issues %li
= link_to_label(label, subject: subject) do
View open issues
- if current_user - if current_user
%li.label-subscription %li.label-subscription
- if can_subscribe_to_label_in_different_levels?(label) - if can_subscribe_to_label_in_different_levels?(label)
...@@ -35,13 +39,20 @@ ...@@ -35,13 +39,20 @@
%li %li
= link_to 'Edit', edit_label_path(label) = link_to 'Edit', edit_label_path(label)
%li %li
= link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'} = link_to 'Delete',
destroy_label_path(label),
title: 'Delete',
method: :delete,
data: {confirm: 'Remove this label? Are you sure?'},
class: 'text-danger'
.pull-right.hidden-xs.hidden-sm.hidden-md .pull-right.hidden-xs.hidden-sm.hidden-md
= link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do - if show_label_merge_requests_link
view merge requests = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do view merge requests
view open issues - if show_label_issues_link
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do
view open issues
- if current_user - if current_user
.label-subscription.inline .label-subscription.inline
......
- show_roles = local_assigns.fetch(:show_roles, true) - show_roles = local_assigns.fetch(:show_roles, true)
- show_controls = local_assigns.fetch(:show_controls, true) - show_controls = local_assigns.fetch(:show_controls, true)
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false) - force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
- member = local_assigns.fetch(:member)
- user = local_assigns.fetch(:user, member.user) - user = local_assigns.fetch(:user, member.user)
- source = member.source - source = member.source
- can_admin_member = can?(current_user, action_member_permission(:update, member), member)
%li.member{ class: dom_class(member), id: dom_id(member) } %li.member{ class: dom_class(member), id: dom_id(member) }
%span.list-item-name %span.list-item-name
...@@ -50,18 +50,17 @@ ...@@ -50,18 +50,17 @@
.controls.member-controls .controls.member-controls
- if show_controls && member.source == current_resource - if show_controls && member.source == current_resource
- if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source) - if member.can_resend_invite?
= link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]), = link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
method: :post, method: :post,
class: 'btn btn-default prepend-left-10 hidden-xs', class: 'btn btn-default prepend-left-10 hidden-xs',
title: 'Resend invite' title: 'Resend invite'
- if user != current_user && can_admin_member - if user != current_user && member.can_update?
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level = f.hidden_field :access_level
.member-form-control.dropdown.append-right-5 .member-form-control.dropdown.append-right-5
%button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button", %button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button",
disabled: !can_admin_member,
data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } } data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } }
%span.dropdown-toggle-text %span.dropdown-toggle-text
= member.human_access = member.human_access
...@@ -70,23 +69,22 @@ ...@@ -70,23 +69,22 @@
= dropdown_title("Change permissions") = dropdown_title("Change permissions")
.dropdown-content .dropdown-content
%ul %ul
- member.class.access_level_roles.each do |role, role_id| - member.access_level_roles.each do |role, role_id|
%li %li
= link_to role, "javascript:void(0)", = link_to role, "javascript:void(0)",
class: ("is-active" if member.access_level == role_id), class: ("is-active" if member.access_level == role_id),
data: { id: role_id, el_id: dom_id(member) } data: { id: role_id, el_id: dom_id(member) }
.prepend-left-5.clearable-input.member-form-control .prepend-left-5.clearable-input.member-form-control
= f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member, data: { el_id: dom_id(member) } = f.text_field :expires_at,
class: 'form-control js-access-expiration-date js-member-update-control',
placeholder: 'Expiration date',
id: "member_expires_at_#{member.id}",
data: { el_id: dom_id(member) }
%i.clear-icon.js-clear-input %i.clear-icon.js-clear-input
- else - else
%span.member-access-text= member.human_access %span.member-access-text= member.human_access
- if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source) - if member.can_approve?
= link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
method: :post,
class: 'btn btn-default prepend-left-10 visible-xs-block'
- elsif member.request? && can_admin_member
= link_to polymorphic_path([:approve_access_request, member]), = link_to polymorphic_path([:approve_access_request, member]),
method: :post, method: :post,
class: 'btn btn-success prepend-left-10', class: 'btn btn-success prepend-left-10',
...@@ -96,7 +94,7 @@ ...@@ -96,7 +94,7 @@
- unless force_mobile_view - unless force_mobile_view
= icon('check inverse', class: 'hidden-xs') = icon('check inverse', class: 'hidden-xs')
- if can?(current_user, action_member_permission(:destroy, member), member) - if member.can_remove?
- if current_user == user - if current_user == user
= link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]), = link_to icon('sign-out', text: 'Leave'), polymorphic_path([:leave, member.source, :members]),
method: :delete, method: :delete,
......
- membership_source = local_assigns.fetch(:membership_source)
- requesters = local_assigns.fetch(:requesters)
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false) - force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
- if requesters.any? - return if requesters.empty?
.panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
.panel-heading .panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
Users requesting access to .panel-heading
%strong= membership_source.name Users requesting access to
%span.badge= requesters.size %strong= membership_source.name
%ul.content-list.members-list %span.badge= requesters.size
= render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view } %ul.content-list.members-list
= render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
- if note.is_a?(DiffNote) && note.on_image? - if note.is_a?(DiffNote) && note.on_image?
- if show_image_comment_badge && note_counter == 0 - if show_image_comment_badge && note_counter == 0
-# Only show this for the first comment in the discussion -# Only show this for the first comment in the discussion
%span.image-comment-badge.inverted %span.image-comment-badge
= icon('comment-o') = sprite_icon('image-comment-dark')
- elsif note_counter == 0 - elsif note_counter == 0
- counter = badge_counter if local_assigns[:badge_counter] - counter = badge_counter if local_assigns[:badge_counter]
- badge_class = "hidden" if @fresh_discussion || counter.nil? - badge_class = "hidden" if @fresh_discussion || counter.nil?
......
---
title: Refactor member view using a Presenter
merge_request: 9645
author: TM Lee
---
title: Update comment on image cursor and icons
merge_request: 15760
author:
type: fixed
---
title: Use app host instead of asset host when rendering image blob or diff
merge_request:
author:
type: fixed
---
title: Fix error during schema dump.
merge_request: 15866
author:
type: fixed
---
title: Present multiple clusters in a single list instead of a tabbed view
merge_request: 15669
author:
type: changed
---
title: Treat empty markdown and html strings as valid cached text, not missing cache
that needs to be updated
merge_request:
author:
type: performance
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment