Commit 9429514e authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge branch '35616-move-k8-to-cluster-page' into cluster-page-with-list-clusters

parents b9d8d85e 3138fcdc
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'rails', '4.2.8' gem 'rails', '4.2.10'
gem 'rails-deprecated_sanitizer', '~> 1.0.3' gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
...@@ -400,7 +400,7 @@ group :ed25519 do ...@@ -400,7 +400,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.54.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.58.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -4,38 +4,38 @@ GEM ...@@ -4,38 +4,38 @@ GEM
RedCloth (4.3.2) RedCloth (4.3.2)
abstract_type (0.0.7) abstract_type (0.0.7)
ace-rails-ap (4.1.2) ace-rails-ap (4.1.2)
actionmailer (4.2.8) actionmailer (4.2.10)
actionpack (= 4.2.8) actionpack (= 4.2.10)
actionview (= 4.2.8) actionview (= 4.2.10)
activejob (= 4.2.8) activejob (= 4.2.10)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.8) actionpack (4.2.10)
actionview (= 4.2.8) actionview (= 4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
rack (~> 1.6) rack (~> 1.6)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.8) actionview (4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.3) rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (4.2.8) activejob (4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
globalid (>= 0.3.0) globalid (>= 0.3.0)
activemodel (4.2.8) activemodel (4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
builder (~> 3.1) builder (~> 3.1)
activerecord (4.2.8) activerecord (4.2.10)
activemodel (= 4.2.8) activemodel (= 4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
arel (~> 6.0) arel (~> 6.0)
activerecord_sane_schema_dumper (0.2) activerecord_sane_schema_dumper (0.2)
rails (>= 4, < 5) rails (>= 4, < 5)
activesupport (4.2.8) activesupport (4.2.10)
i18n (~> 0.7) i18n (~> 0.7)
minitest (~> 5.1) minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4) thread_safe (~> 0.3, >= 0.3.4)
...@@ -276,7 +276,7 @@ GEM ...@@ -276,7 +276,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.54.0) gitaly-proto (0.58.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -300,8 +300,8 @@ GEM ...@@ -300,8 +300,8 @@ GEM
omniauth (~> 1.3) omniauth (~> 1.3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1) pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5) rubyntlm (~> 0.5)
globalid (0.3.7) globalid (0.4.1)
activesupport (>= 4.1.0) activesupport (>= 4.2.0)
gollum-grit_adapter (1.0.1) gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1) gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7) gollum-lib (4.2.7)
...@@ -400,7 +400,8 @@ GEM ...@@ -400,7 +400,8 @@ GEM
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.2) httpclient (2.8.2)
i18n (0.8.6) i18n (0.9.1)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2) ice_nine (0.11.2)
influxdb (0.2.3) influxdb (0.2.3)
cause cause
...@@ -474,8 +475,8 @@ GEM ...@@ -474,8 +475,8 @@ GEM
railties (>= 4, < 5.2) railties (>= 4, < 5.2)
loofah (2.0.3) loofah (2.0.3)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.6.6) mail (2.7.0)
mime-types (>= 1.16, < 4) mini_mime (>= 0.1.1)
mail_room (0.9.1) mail_room (0.9.1)
memoist (0.16.0) memoist (0.16.0)
memoizable (0.4.2) memoizable (0.4.2)
...@@ -573,8 +574,8 @@ GEM ...@@ -573,8 +574,8 @@ GEM
parallel (1.12.0) parallel (1.12.0)
paranoia (2.3.1) paranoia (2.3.1)
activerecord (>= 4.0, < 5.2) activerecord (>= 4.0, < 5.2)
parser (2.4.0.0) parser (2.4.0.2)
ast (~> 2.2) ast (~> 2.3)
parslet (1.5.0) parslet (1.5.0)
blankslate (~> 2.0) blankslate (~> 2.0)
path_expander (1.0.1) path_expander (1.0.1)
...@@ -656,16 +657,16 @@ GEM ...@@ -656,16 +657,16 @@ GEM
rack rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.8) rails (4.2.10)
actionmailer (= 4.2.8) actionmailer (= 4.2.10)
actionpack (= 4.2.8) actionpack (= 4.2.10)
actionview (= 4.2.8) actionview (= 4.2.10)
activejob (= 4.2.8) activejob (= 4.2.10)
activemodel (= 4.2.8) activemodel (= 4.2.10)
activerecord (= 4.2.8) activerecord (= 4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.2.8) railties (= 4.2.10)
sprockets-rails sprockets-rails
rails-deprecated_sanitizer (1.0.3) rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha) activesupport (>= 4.2.0.alpha)
...@@ -678,15 +679,15 @@ GEM ...@@ -678,15 +679,15 @@ GEM
rails-i18n (4.0.9) rails-i18n (4.0.9)
i18n (~> 0.7) i18n (~> 0.7)
railties (~> 4.0) railties (~> 4.0)
railties (4.2.8) railties (4.2.10)
actionpack (= 4.2.8) actionpack (= 4.2.10)
activesupport (= 4.2.8) activesupport (= 4.2.10)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.2.2) rainbow (2.2.2)
rake rake
raindrops (0.18.0) raindrops (0.18.0)
rake (12.1.0) rake (12.3.0)
rblineprof (0.3.6) rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3) debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2) rbnacl (4.0.2)
...@@ -873,7 +874,7 @@ GEM ...@@ -873,7 +874,7 @@ GEM
sprockets (3.7.1) sprockets (3.7.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-rails (3.2.0) sprockets-rails (3.2.1)
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
...@@ -911,7 +912,7 @@ GEM ...@@ -911,7 +912,7 @@ GEM
truncato (0.7.10) truncato (0.7.10)
htmlentities (~> 4.3.1) htmlentities (~> 4.3.1)
nokogiri (~> 1.8.0, >= 1.7.0) nokogiri (~> 1.8.0, >= 1.7.0)
tzinfo (1.2.3) tzinfo (1.2.4)
thread_safe (~> 0.1) thread_safe (~> 0.1)
u2f (0.2.1) u2f (0.2.1)
uber (0.1.0) uber (0.1.0)
...@@ -1036,7 +1037,7 @@ DEPENDENCIES ...@@ -1036,7 +1037,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.54.0) gitaly-proto (~> 0.58.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.2)
...@@ -1118,7 +1119,7 @@ DEPENDENCIES ...@@ -1118,7 +1119,7 @@ DEPENDENCIES
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0) rack-proxy (~> 0.6.0)
rails (= 4.2.8) rails (= 4.2.10)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rails-i18n (~> 4.0.9) rails-i18n (~> 4.0.9)
rainbow (~> 2.2) rainbow (~> 2.2)
......
...@@ -20,6 +20,7 @@ class ListIssue { ...@@ -20,6 +20,7 @@ class ListIssue {
this.isFetching = { this.isFetching = {
subscriptions: true, subscriptions: true,
}; };
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
...@@ -86,6 +87,10 @@ class ListIssue { ...@@ -86,6 +87,10 @@ class ListIssue {
this.isFetching[key] = value; this.isFetching[key] = value;
} }
setLoadingState(key, value) {
this.isLoading[key] = value;
}
update (url) { update (url) {
const data = { const data = {
issue: { issue: {
......
...@@ -3,3 +3,4 @@ import './polyfills'; ...@@ -3,3 +3,4 @@ import './polyfills';
import './jquery'; import './jquery';
import './bootstrap'; import './bootstrap';
import './vue'; import './vue';
import '../lib/utils/axios_utils';
...@@ -514,10 +514,11 @@ GitLabDropdown = (function() { ...@@ -514,10 +514,11 @@ GitLabDropdown = (function() {
const dropdownToggle = this.dropdown.find('.dropdown-menu-toggle'); const dropdownToggle = this.dropdown.find('.dropdown-menu-toggle');
const hasFilterBulkUpdate = dropdownToggle.hasClass('js-filter-bulk-update'); const hasFilterBulkUpdate = dropdownToggle.hasClass('js-filter-bulk-update');
const shouldRefreshOnOpen = dropdownToggle.hasClass('js-gl-dropdown-refresh-on-open');
const hasMultiSelect = dropdownToggle.hasClass('js-multiselect'); const hasMultiSelect = dropdownToggle.hasClass('js-multiselect');
// Makes indeterminate items effective // Makes indeterminate items effective
if (this.fullData && hasFilterBulkUpdate) { if (this.fullData && (shouldRefreshOnOpen || hasFilterBulkUpdate)) {
this.parseData(this.fullData); this.parseData(this.fullData);
} }
......
<script> <script>
import Icon from '../../vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import Issuable from '~/vue_shared/mixins/issuable';
export default { export default {
component: { mixins: [
Issuable,
],
components: {
Icon, Icon,
}, },
}; };
...@@ -16,7 +20,7 @@ ...@@ -16,7 +20,7 @@
:size="16" :size="16"
class="icon"> class="icon">
</icon> </icon>
<span>This issue is locked. Only <b>project members</b> can comment.</span> <span>This {{ issuableDisplayName }} is locked. Only <b>project members</b> can comment.</span>
</span> </span>
</div> </div>
</template> </template>
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
import * as constants from '../constants'; import * as constants from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue'; import noteSignedOutWidget from './note_signed_out_widget.vue';
import issueDiscussionLockedWidget from './issue_discussion_locked_widget.vue'; import discussionLockedWidget from './discussion_locked_widget.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue'; import markdownField from '../../vue_shared/components/markdown/field.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 issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
}, },
components: { components: {
issueWarning, issueWarning,
issueNoteSignedOutWidget, noteSignedOutWidget,
issueDiscussionLockedWidget, discussionLockedWidget,
markdownField, markdownField,
userAvatarLink, userAvatarLink,
}, },
...@@ -240,8 +240,11 @@ ...@@ -240,8 +240,11 @@
<template> <template>
<div> <div>
<issue-note-signed-out-widget v-if="!isLoggedIn" /> <note-signed-out-widget v-if="!isLoggedIn" />
<issue-discussion-locked-widget v-else-if="!canCreateNote" /> <discussion-locked-widget
issuable-type="issue"
v-else-if="!canCreateNote"
/>
<ul <ul
v-else v-else
class="notes notes-form timeline"> class="notes notes-form timeline">
......
...@@ -4,10 +4,9 @@ ...@@ -4,10 +4,9 @@
import { SYSTEM_NOTE } from '../constants'; import { SYSTEM_NOTE } from '../constants';
import issueNote from './issue_note.vue'; 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 issueNoteHeader from './issue_note_header.vue'; import noteHeader from './note_header.vue';
import issueNoteActions from './issue_note_actions.vue'; import noteSignedOutWidget from './note_signed_out_widget.vue';
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue'; import noteEditedText from './note_edited_text.vue';
import issueNoteEditedText from './issue_note_edited_text.vue';
import issueNoteForm from './issue_note_form.vue'; import issueNoteForm from './issue_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';
...@@ -28,10 +27,9 @@ ...@@ -28,10 +27,9 @@
components: { components: {
issueNote, issueNote,
userAvatarLink, userAvatarLink,
issueNoteHeader, noteHeader,
issueNoteActions, noteSignedOutWidget,
issueNoteSignedOutWidget, noteEditedText,
issueNoteEditedText,
issueNoteForm, issueNoteForm,
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
...@@ -171,7 +169,7 @@ ...@@ -171,7 +169,7 @@
<div class="timeline-content"> <div class="timeline-content">
<div class="discussion"> <div class="discussion">
<div class="discussion-header"> <div class="discussion-header">
<issue-note-header <note-header
:author="author" :author="author"
:created-at="discussion.created_at" :created-at="discussion.created_at"
:note-id="discussion.id" :note-id="discussion.id"
...@@ -179,8 +177,8 @@ ...@@ -179,8 +177,8 @@
@toggleHandler="toggleDiscussionHandler" @toggleHandler="toggleDiscussionHandler"
action-text="started a discussion" action-text="started a discussion"
class="discussion" class="discussion"
/> />
<issue-note-edited-text <note-edited-text
v-if="lastUpdatedAt" v-if="lastUpdatedAt"
:edited-at="lastUpdatedAt" :edited-at="lastUpdatedAt"
:edited-by="lastUpdatedBy" :edited-by="lastUpdatedBy"
...@@ -220,7 +218,7 @@ ...@@ -220,7 +218,7 @@
@cancelFormEdition="cancelReplyForm" @cancelFormEdition="cancelReplyForm"
ref="noteForm" ref="noteForm"
/> />
<issue-note-signed-out-widget v-if="!canReply" /> <note-signed-out-widget v-if="!canReply" />
</div> </div>
</div> </div>
</div> </div>
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import Flash from '../../flash'; import Flash from '../../flash';
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 issueNoteHeader from './issue_note_header.vue'; import noteHeader from './note_header.vue';
import issueNoteActions from './issue_note_actions.vue'; import noteActions from './note_actions.vue';
import issueNoteBody from './issue_note_body.vue'; import issueNoteBody from './issue_note_body.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -23,8 +23,8 @@ ...@@ -23,8 +23,8 @@
}, },
components: { components: {
userAvatarLink, userAvatarLink,
issueNoteHeader, noteHeader,
issueNoteActions, noteActions,
issueNoteBody, issueNoteBody,
}, },
computed: { computed: {
...@@ -155,13 +155,13 @@ ...@@ -155,13 +155,13 @@
</div> </div>
<div class="timeline-content"> <div class="timeline-content">
<div class="note-header"> <div class="note-header">
<issue-note-header <note-header
:author="author" :author="author"
:created-at="note.created_at" :created-at="note.created_at"
:note-id="note.id" :note-id="note.id"
action-text="commented" action-text="commented"
/> />
<issue-note-actions <note-actions
:author-id="author.id" :author-id="author.id"
:note-id="note.id" :note-id="note.id"
:access-level="note.human_access" :access-level="note.human_access"
......
<script> <script>
import issueNoteEditedText from './issue_note_edited_text.vue'; import noteEditedText from './note_edited_text.vue';
import issueNoteAwardsList from './issue_note_awards_list.vue'; import noteAwardsList from './note_awards_list.vue';
import issueNoteAttachment from './issue_note_attachment.vue'; import noteAttachment from './note_attachment.vue';
import issueNoteForm from './issue_note_form.vue'; import issueNoteForm from './issue_note_form.vue';
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import autosave from '../mixins/autosave'; import autosave from '../mixins/autosave';
...@@ -26,9 +26,9 @@ ...@@ -26,9 +26,9 @@
autosave, autosave,
], ],
components: { components: {
issueNoteEditedText, noteEditedText,
issueNoteAwardsList, noteAwardsList,
issueNoteAttachment, noteAttachment,
issueNoteForm, issueNoteForm,
}, },
computed: { computed: {
...@@ -101,20 +101,20 @@ ...@@ -101,20 +101,20 @@
v-model="note.note" v-model="note.note"
:data-update-url="note.path" :data-update-url="note.path"
class="hidden js-task-list-field"></textarea> class="hidden js-task-list-field"></textarea>
<issue-note-edited-text <note-edited-text
v-if="note.last_edited_at" v-if="note.last_edited_at"
:edited-at="note.last_edited_at" :edited-at="note.last_edited_at"
:edited-by="note.last_edited_by" :edited-by="note.last_edited_by"
action-text="Edited" action-text="Edited"
/> />
<issue-note-awards-list <note-awards-list
v-if="note.award_emoji.length" v-if="note.award_emoji.length"
:note-id="note.id" :note-id="note.id"
:note-author-id="note.author.id" :note-author-id="note.author.id"
:awards="note.award_emoji" :awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path" :toggle-award-path="note.toggle_award_path"
/> />
<issue-note-attachment <note-attachment
v-if="note.attachment" v-if="note.attachment"
:attachment="note.attachment" :attachment="note.attachment"
/> />
......
...@@ -5,11 +5,11 @@ ...@@ -5,11 +5,11 @@
import emojiSmiley from 'icons/_emoji_smiley.svg'; import emojiSmiley from 'icons/_emoji_smiley.svg';
import editSvg from 'icons/_icon_pencil.svg'; import editSvg from 'icons/_icon_pencil.svg';
import ellipsisSvg from 'icons/_ellipsis_v.svg'; import ellipsisSvg from 'icons/_ellipsis_v.svg';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
export default { export default {
name: 'issueNoteActions', name: 'noteActions',
props: { props: {
authorId: { authorId: {
type: Number, type: Number,
......
<script> <script>
export default { export default {
name: 'issueNoteAttachment', name: 'noteAttachment',
props: { props: {
attachment: { attachment: {
type: Object, type: Object,
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
export default { export default {
name: 'singInLinksNotes',
computed: { computed: {
...mapGetters([ ...mapGetters([
'getNotesDataByProp', 'getNotesDataByProp',
......
...@@ -78,11 +78,13 @@ ...@@ -78,11 +78,13 @@
<div class="ci-job-component"> <div class="ci-job-component">
<a <a
v-tooltip v-tooltip
v-if="job.status.details_path" v-if="job.status.has_details"
:href="job.status.details_path" :href="job.status.details_path"
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body"> data-container="body"
class="js-pipeline-graph-job-link"
>
<job-name-component <job-name-component
:name="job.name" :name="job.name"
...@@ -95,7 +97,8 @@ ...@@ -95,7 +97,8 @@
v-tooltip v-tooltip
:title="tooltipText" :title="tooltipText"
:class="cssClassJobName" :class="cssClassJobName"
data-container="body"> data-container="body"
>
<job-name-component <job-name-component
:name="job.name" :name="job.name"
......
...@@ -15,7 +15,7 @@ import Cookies from 'js-cookie'; ...@@ -15,7 +15,7 @@ import Cookies from 'js-cookie';
Sidebar.prototype.removeListeners = function () { Sidebar.prototype.removeListeners = function () {
this.sidebar.off('click', '.sidebar-collapsed-icon'); this.sidebar.off('click', '.sidebar-collapsed-icon');
$('.dropdown').off('hidden.gl.dropdown'); this.sidebar.off('hidden.gl.dropdown');
$('.dropdown').off('loading.gl.dropdown'); $('.dropdown').off('loading.gl.dropdown');
$('.dropdown').off('loaded.gl.dropdown'); $('.dropdown').off('loaded.gl.dropdown');
$(document).off('click', '.js-sidebar-toggle'); $(document).off('click', '.js-sidebar-toggle');
...@@ -25,7 +25,7 @@ import Cookies from 'js-cookie'; ...@@ -25,7 +25,7 @@ import Cookies from 'js-cookie';
const $document = $(document); const $document = $(document);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
$('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden); this.sidebar.on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading); $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
...@@ -180,7 +180,7 @@ import Cookies from 'js-cookie'; ...@@ -180,7 +180,7 @@ import Cookies from 'js-cookie';
var $block, sidebar; var $block, sidebar;
sidebar = e.data; sidebar = e.data;
e.preventDefault(); e.preventDefault();
$block = $(this).closest('.block'); $block = $(e.target).closest('.block');
return sidebar.sidebarDropdownHidden($block); return sidebar.sidebarDropdownHidden($block);
}; };
......
...@@ -18,11 +18,6 @@ export default { ...@@ -18,11 +18,6 @@ export default {
required: true, required: true,
type: Function, type: Function,
}, },
issuableType: {
required: true,
type: String,
},
}, },
mixins: [ mixins: [
...@@ -39,13 +34,13 @@ export default { ...@@ -39,13 +34,13 @@ export default {
<div class="dropdown open"> <div class="dropdown open">
<div class="dropdown-menu sidebar-item-warning-message"> <div class="dropdown-menu sidebar-item-warning-message">
<p class="text" v-if="isLocked"> <p class="text" v-if="isLocked">
Unlock this {{ issuableDisplayName(issuableType) }}? Unlock this {{ issuableDisplayName }}?
<strong>Everyone</strong> <strong>Everyone</strong>
will be able to comment. will be able to comment.
</p> </p>
<p class="text" v-else> <p class="text" v-else>
Lock this {{ issuableDisplayName(issuableType) }}? Lock this {{ issuableDisplayName }}?
Only Only
<strong>project members</strong> <strong>project members</strong>
will be able to comment. will be able to comment.
......
...@@ -23,11 +23,6 @@ export default { ...@@ -23,11 +23,6 @@ export default {
return mediatorObject.service && mediatorObject.service.update && mediatorObject.store; return mediatorObject.service && mediatorObject.service.update && mediatorObject.store;
}, },
}, },
issuableType: {
required: true,
type: String,
},
}, },
mixins: [ mixins: [
...@@ -59,7 +54,7 @@ export default { ...@@ -59,7 +54,7 @@ export default {
discussion_locked: locked, discussion_locked: locked,
}) })
.then(() => location.reload()) .then(() => location.reload())
.catch(() => Flash(this.__(`Something went wrong trying to change the locked state of this ${this.issuableDisplayName(this.issuableType)}`))); .catch(() => Flash(this.__(`Something went wrong trying to change the locked state of this ${this.issuableDisplayName}`)));
}, },
}, },
}; };
...@@ -77,7 +72,7 @@ export default { ...@@ -77,7 +72,7 @@ export default {
</div> </div>
<div class="title hide-collapsed"> <div class="title hide-collapsed">
Lock {{issuableDisplayName(issuableType) }} Lock {{ issuableDisplayName }}
<button <button
v-if="isEditable" v-if="isEditable"
class="pull-right lock-edit btn btn-blank" class="pull-right lock-edit btn btn-blank"
......
import Vue from 'vue';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import SidebarAssignees from './components/assignees/sidebar_assignees';
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
import SidebarMoveIssue from './lib/sidebar_move_issue';
import LockIssueSidebar from './components/lock/lock_issue_sidebar.vue';
import sidebarParticipants from './components/participants/sidebar_participants.vue';
import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptions.vue';
import Translate from '../vue_shared/translate';
Vue.use(Translate);
function mountConfidentialComponent(mediator) {
const el = document.getElementById('js-confidential-entry-point');
if (!el) return;
const dataNode = document.getElementById('js-confidential-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
const ConfidentialComp = Vue.extend(ConfidentialIssueSidebar);
new ConfidentialComp({
propsData: {
isConfidential: initialData.is_confidential,
isEditable: initialData.is_editable,
service: mediator.service,
},
}).$mount(el);
}
function mountLockComponent(mediator) {
const el = document.getElementById('js-lock-entry-point');
if (!el) return;
const dataNode = document.getElementById('js-lock-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
const LockComp = Vue.extend(LockIssueSidebar);
new LockComp({
propsData: {
isLocked: initialData.is_locked,
isEditable: initialData.is_editable,
mediator,
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
},
}).$mount(el);
}
function mountParticipantsComponent() {
const el = document.querySelector('.js-sidebar-participants-entry-point');
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
sidebarParticipants,
},
render: createElement => createElement('sidebar-participants', {}),
});
}
function mountSubscriptionsComponent() {
const el = document.querySelector('.js-sidebar-subscriptions-entry-point');
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
sidebarSubscriptions,
},
render: createElement => createElement('sidebar-subscriptions', {}),
});
}
function mount(mediator) {
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees');
// 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);
}
mountConfidentialComponent(mediator);
mountLockComponent(mediator);
mountParticipantsComponent();
mountSubscriptionsComponent();
new SidebarMoveIssue(
mediator,
$('.js-move-issue'),
$('.js-move-issue-confirmation-button'),
).init();
new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker');
}
export default mount;
import Vue from 'vue';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import SidebarAssignees from './components/assignees/sidebar_assignees';
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
import SidebarMoveIssue from './lib/sidebar_move_issue';
import LockIssueSidebar from './components/lock/lock_issue_sidebar.vue';
import sidebarParticipants from './components/participants/sidebar_participants.vue';
import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptions.vue';
import Translate from '../vue_shared/translate';
import Mediator from './sidebar_mediator'; import Mediator from './sidebar_mediator';
import mountSidebar from './mount_sidebar';
Vue.use(Translate);
function mountConfidentialComponent(mediator) {
const el = document.getElementById('js-confidential-entry-point');
if (!el) return;
const dataNode = document.getElementById('js-confidential-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
const ConfidentialComp = Vue.extend(ConfidentialIssueSidebar);
new ConfidentialComp({
propsData: {
isConfidential: initialData.is_confidential,
isEditable: initialData.is_editable,
service: mediator.service,
},
}).$mount(el);
}
function mountLockComponent(mediator) {
const el = document.getElementById('js-lock-entry-point');
if (!el) return;
const dataNode = document.getElementById('js-lock-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
const LockComp = Vue.extend(LockIssueSidebar);
new LockComp({
propsData: {
isLocked: initialData.is_locked,
isEditable: initialData.is_editable,
mediator,
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
},
}).$mount(el);
}
function mountParticipantsComponent() {
const el = document.querySelector('.js-sidebar-participants-entry-point');
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
sidebarParticipants,
},
render: createElement => createElement('sidebar-participants', {}),
});
}
function mountSubscriptionsComponent() {
const el = document.querySelector('.js-sidebar-subscriptions-entry-point');
if (!el) return;
// eslint-disable-next-line no-new
new Vue({
el,
components: {
sidebarSubscriptions,
},
render: createElement => createElement('sidebar-subscriptions', {}),
});
}
function domContentLoaded() { function domContentLoaded() {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
const mediator = new Mediator(sidebarOptions); const mediator = new Mediator(sidebarOptions);
mediator.fetch(); mediator.fetch();
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees'); mountSidebar(mediator);
// 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);
}
mountConfidentialComponent(mediator);
mountLockComponent(mediator);
mountParticipantsComponent();
mountSubscriptionsComponent();
new SidebarMoveIssue(
mediator,
$('.js-move-issue'),
$('.js-move-issue-confirmation-button'),
).init();
new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker');
} }
document.addEventListener('DOMContentLoaded', domContentLoaded); document.addEventListener('DOMContentLoaded', domContentLoaded);
......
...@@ -5,19 +5,23 @@ import Store from './stores/sidebar_store'; ...@@ -5,19 +5,23 @@ import Store from './stores/sidebar_store';
export default class SidebarMediator { export default class SidebarMediator {
constructor(options) { constructor(options) {
if (!SidebarMediator.singleton) { if (!SidebarMediator.singleton) {
this.store = new Store(options); this.initSingleton(options);
this.service = new Service({
endpoint: options.endpoint,
toggleSubscriptionEndpoint: options.toggleSubscriptionEndpoint,
moveIssueEndpoint: options.moveIssueEndpoint,
projectsAutocompleteEndpoint: options.projectsAutocompleteEndpoint,
});
SidebarMediator.singleton = this;
} }
return SidebarMediator.singleton; return SidebarMediator.singleton;
} }
initSingleton(options) {
this.store = new Store(options);
this.service = new Service({
endpoint: options.endpoint,
toggleSubscriptionEndpoint: options.toggleSubscriptionEndpoint,
moveIssueEndpoint: options.moveIssueEndpoint,
projectsAutocompleteEndpoint: options.projectsAutocompleteEndpoint,
});
SidebarMediator.singleton = this;
}
assignYourself() { assignYourself() {
this.store.addAssignee(this.store.currentUser); this.store.addAssignee(this.store.currentUser);
} }
...@@ -35,17 +39,21 @@ export default class SidebarMediator { ...@@ -35,17 +39,21 @@ export default class SidebarMediator {
} }
fetch() { fetch() {
this.service.get() return this.service.get()
.then(response => response.json()) .then(response => response.json())
.then((data) => { .then((data) => {
this.store.setAssigneeData(data); this.processFetchedData(data);
this.store.setTimeTrackingData(data);
this.store.setParticipantsData(data);
this.store.setSubscriptionsData(data);
}) })
.catch(() => new Flash('Error occurred when fetching sidebar data')); .catch(() => new Flash('Error occurred when fetching sidebar data'));
} }
processFetchedData(data) {
this.store.setAssigneeData(data);
this.store.setTimeTrackingData(data);
this.store.setParticipantsData(data);
this.store.setSubscriptionsData(data);
}
toggleSubscription() { toggleSubscription() {
this.store.setFetchingState('subscriptions', true); this.store.setFetchingState('subscriptions', true);
return this.service.toggleSubscription() return this.service.toggleSubscription()
......
...@@ -15,6 +15,7 @@ export default class SidebarStore { ...@@ -15,6 +15,7 @@ export default class SidebarStore {
participants: true, participants: true,
subscriptions: true, subscriptions: true,
}; };
this.isLoading = {};
this.autocompleteProjects = []; this.autocompleteProjects = [];
this.moveToProjectId = 0; this.moveToProjectId = 0;
this.isLockDialogOpen = false; this.isLockDialogOpen = false;
...@@ -55,6 +56,10 @@ export default class SidebarStore { ...@@ -55,6 +56,10 @@ export default class SidebarStore {
this.isFetching[key] = value; this.isFetching[key] = value;
} }
setLoadingState(key, value) {
this.isLoading[key] = value;
}
addAssignee(assignee) { addAssignee(assignee) {
if (!this.findAssignee(assignee)) { if (!this.findAssignee(assignee)) {
this.assignees.push(assignee); this.assignees.push(assignee);
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
* /> * />
*/ */
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import issueNoteHeader from '../../../notes/components/issue_note_header.vue'; import noteHeader from '~/notes/components/note_header.vue';
import { spriteIcon } from '../../../lib/utils/common_utils'; import { spriteIcon } from '../../../lib/utils/common_utils';
export default { export default {
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
}, },
}, },
components: { components: {
issueNoteHeader, noteHeader,
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
...@@ -60,12 +60,12 @@ ...@@ -60,12 +60,12 @@
</div> </div>
<div class="timeline-content"> <div class="timeline-content">
<div class="note-header"> <div class="note-header">
<issue-note-header <note-header
:author="note.author" :author="note.author"
:created-at="note.created_at" :created-at="note.created_at"
:note-id="note.id" :note-id="note.id"
:action-text-html="note.note_html" :action-text-html="note.note_html"
/> />
</div> </div>
</div> </div>
</div> </div>
......
export default { export default {
methods: { props: {
issuableDisplayName(issuableType) { issuableType: {
const displayName = issuableType.replace(/_/, ' '); required: true,
type: String,
},
},
return this.__ ? this.__(displayName) : displayName; computed: {
issuableDisplayName() {
return this.issuableType.replace(/_/g, ' ');
}, },
}, },
}; };
...@@ -39,7 +39,6 @@ ...@@ -39,7 +39,6 @@
color: $brand-info; color: $brand-info;
} }
.underlined-link { text-decoration: underline; }
.hint { font-style: italic; color: $hint-color; } .hint { font-style: italic; color: $hint-color; }
.light { color: $common-gray; } .light { color: $common-gray; }
......
...@@ -14,6 +14,5 @@ ...@@ -14,6 +14,5 @@
&:hover { &:hover {
background-color: $user-mention-bg-hover; background-color: $user-mention-bg-hover;
text-decoration: none;
} }
} }
...@@ -20,6 +20,11 @@ ...@@ -20,6 +20,11 @@
.ref-name { .ref-name {
font-size: 12px; font-size: 12px;
&:hover {
text-decoration: underline;
color: $gl-text-color;
}
} }
} }
......
...@@ -110,6 +110,10 @@ ...@@ -110,6 +110,10 @@
padding: 6px 10px; padding: 6px 10px;
border-radius: $label-border-radius; border-radius: $label-border-radius;
} }
&:hover .color-label {
text-decoration: underline;
}
} }
&.has-labels { &.has-labels {
...@@ -174,6 +178,14 @@ ...@@ -174,6 +178,14 @@
color: $gray-darkest; color: $gray-darkest;
} }
} }
&.assignee {
.author_link:hover {
.author {
text-decoration: underline;
}
}
}
} }
.block-first { .block-first {
...@@ -468,7 +480,6 @@ ...@@ -468,7 +480,6 @@
a:not(.btn-retry) { a:not(.btn-retry) {
&:hover { &:hover {
color: $md-link-color; color: $md-link-color;
text-decoration: none;
.avatar { .avatar {
border-color: rgba($avatar-border, .2); border-color: rgba($avatar-border, .2);
......
...@@ -208,7 +208,6 @@ ul.notes { ...@@ -208,7 +208,6 @@ ul.notes {
a { a {
color: $gl-link-color; color: $gl-link-color;
text-decoration: none;
} }
p { p {
...@@ -395,6 +394,10 @@ ul.notes { ...@@ -395,6 +394,10 @@ ul.notes {
&:focus, &:focus,
&:hover { &:hover {
text-decoration: none; text-decoration: none;
.note-header-author-name {
text-decoration: underline;
}
} }
} }
...@@ -461,6 +464,10 @@ ul.notes { ...@@ -461,6 +464,10 @@ ul.notes {
.system-note-message { .system-note-message {
white-space: normal; white-space: normal;
} }
a:hover {
text-decoration: underline;
}
} }
/** /**
......
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
.profile-link-holder { .profile-link-holder {
display: inline; display: inline;
a { a:not(.text-link) {
text-decoration: none; text-decoration: none;
} }
} }
......
...@@ -724,6 +724,7 @@ a.deploy-project-label { ...@@ -724,6 +724,7 @@ a.deploy-project-label {
&:hover, &:hover,
&:focus { &:focus {
color: $gl-text-color; color: $gl-text-color;
text-decoration: underline;
} }
} }
} }
......
...@@ -124,7 +124,11 @@ ...@@ -124,7 +124,11 @@
&:hover, &:hover,
&.active { &.active {
color: $black; text-decoration: none;
span {
text-decoration: underline;
}
} }
} }
......
...@@ -11,7 +11,7 @@ class Projects::Clusters::UserController < Projects::ApplicationController ...@@ -11,7 +11,7 @@ class Projects::Clusters::UserController < Projects::ApplicationController
def create def create
@cluster = ::Clusters::CreateService @cluster = ::Clusters::CreateService
.new(project, current_user, create_params) .new(project, current_user, create_params)
.execute(nil) .execute
if @cluster.persisted? if @cluster.persisted?
redirect_to project_cluster_path(project, @cluster) redirect_to project_cluster_path(project, @cluster)
......
...@@ -22,7 +22,7 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -22,7 +22,7 @@ class Projects::ClustersController < Projects::ApplicationController
def status def status
respond_to do |format| respond_to do |format|
format.json do format.json do
Gitlab::PollingInterval.set_header(response, interval: 10_000) Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL)
render json: ClusterSerializer render json: ClusterSerializer
.new(project: @project, current_user: @current_user) .new(project: @project, current_user: @current_user)
...@@ -70,7 +70,8 @@ class Projects::ClustersController < Projects::ApplicationController ...@@ -70,7 +70,8 @@ class Projects::ClustersController < Projects::ApplicationController
private private
def cluster def cluster
@cluster ||= project.clusters.find_by(id: params[:id])&.present(current_user: current_user) || render_404 @cluster ||= project.clusters.find(params[:id])
.present(current_user: current_user)
end end
def create_params def create_params
......
...@@ -25,7 +25,7 @@ class UsersFinder ...@@ -25,7 +25,7 @@ class UsersFinder
end end
def execute def execute
users = User.all users = User.all.order_id_desc
users = by_username(users) users = by_username(users)
users = by_search(users) users = by_search(users)
users = by_blocked(users) users = by_blocked(users)
......
...@@ -63,7 +63,7 @@ module CommitsHelper ...@@ -63,7 +63,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link # Returns a link formatted as a commit branch link
def commit_branch_link(url, text) def commit_branch_link(url, text)
link_to(url, class: 'label label-gray ref-name branch-link') do link_to(url, class: 'label label-gray ref-name branch-link') do
icon('code-fork') + " #{text}" icon('code-fork', class: 'append-right-5') + "#{text}"
end end
end end
...@@ -77,7 +77,7 @@ module CommitsHelper ...@@ -77,7 +77,7 @@ module CommitsHelper
# Returns a link formatted as a commit tag link # Returns a link formatted as a commit tag link
def commit_tag_link(url, text) def commit_tag_link(url, text)
link_to(url, class: 'label label-gray ref-name') do link_to(url, class: 'label label-gray ref-name') do
icon('tag') + " #{text}" icon('tag', class: 'append-right-5') + "#{text}"
end end
end end
......
...@@ -113,7 +113,13 @@ module MarkupHelper ...@@ -113,7 +113,13 @@ module MarkupHelper
text = wiki_page.content text = wiki_page.content
return '' unless text.present? return '' unless text.present?
context = { pipeline: :wiki, project: @project, project_wiki: @project_wiki, page_slug: wiki_page.slug } context = {
pipeline: :wiki,
project: @project,
project_wiki: @project_wiki,
page_slug: wiki_page.slug,
issuable_state_filter_enabled: true
}
html = html =
case wiki_page.format case wiki_page.format
......
...@@ -97,11 +97,6 @@ module Clusters ...@@ -97,11 +97,6 @@ module Clusters
return false return false
end end
if managed? && name_changed?
errors.add(:base, "cannot modify cluster name")
return false
end
true true
end end
end end
......
...@@ -289,6 +289,14 @@ class Group < Namespace ...@@ -289,6 +289,14 @@ class Group < Namespace
"#{parent.full_path}/#{path_was}" "#{parent.full_path}/#{path_was}"
end end
def group_member(user)
if group_members.loaded?
group_members.find { |gm| gm.user_id == user.id }
else
group_members.find_by(user_id: user)
end
end
private private
def update_two_factor_requirement def update_two_factor_requirement
......
...@@ -139,7 +139,17 @@ class Namespace < ActiveRecord::Base ...@@ -139,7 +139,17 @@ class Namespace < ActiveRecord::Base
def find_fork_of(project) def find_fork_of(project)
return nil unless project.fork_network return nil unless project.fork_network
project.fork_network.find_forks_in(projects).first if RequestStore.active?
forks_in_namespace = RequestStore.fetch("namespaces:#{id}:forked_projects") do
Hash.new do |found_forks, project|
found_forks[project] = project.fork_network.find_forks_in(projects).first
end
end
forks_in_namespace[project]
else
project.fork_network.find_forks_in(projects).first
end
end end
def lfs_enabled? def lfs_enabled?
......
...@@ -562,8 +562,7 @@ class Project < ActiveRecord::Base ...@@ -562,8 +562,7 @@ class Project < ActiveRecord::Base
if forked? if forked?
RepositoryForkWorker.perform_async(id, RepositoryForkWorker.perform_async(id,
forked_from_project.repository_storage_path, forked_from_project.repository_storage_path,
forked_from_project.full_path, forked_from_project.disk_path)
self.namespace.full_path)
else else
RepositoryImportWorker.perform_async(self.id) RepositoryImportWorker.perform_async(self.id)
end end
...@@ -1114,7 +1113,11 @@ class Project < ActiveRecord::Base ...@@ -1114,7 +1113,11 @@ class Project < ActiveRecord::Base
end end
def project_member(user) def project_member(user)
project_members.find_by(user_id: user) if project_members.loaded?
project_members.find { |member| member.user_id == user.id }
else
project_members.find_by(user_id: user)
end
end end
def default_branch def default_branch
......
...@@ -256,7 +256,7 @@ class Repository ...@@ -256,7 +256,7 @@ class Repository
end end
def diverging_commit_counts(branch) def diverging_commit_counts(branch)
root_ref_hash = raw_repository.rev_parse_target(root_ref).oid 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
# Rugged seems to throw a `ReferenceError` when given branch_names rather # Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes # than SHA-1 hashes
......
...@@ -487,7 +487,11 @@ class User < ActiveRecord::Base ...@@ -487,7 +487,11 @@ class User < ActiveRecord::Base
end end
def two_factor_u2f_enabled? def two_factor_u2f_enabled?
u2f_registrations.exists? if u2f_registrations.loaded?
u2f_registrations.any?
else
u2f_registrations.exists?
end
end end
def namespace_uniq def namespace_uniq
...@@ -998,7 +1002,11 @@ class User < ActiveRecord::Base ...@@ -998,7 +1002,11 @@ class User < ActiveRecord::Base
end end
def notification_settings_for(source) def notification_settings_for(source)
notification_settings.find_or_initialize_by(source: source) if notification_settings.loaded?
notification_settings.find { |notification| notification.source == source }
else
notification_settings.find_or_initialize_by(source: source)
end
end end
# Lazy load global notification setting # Lazy load global notification setting
......
...@@ -12,8 +12,12 @@ class BaseCountService ...@@ -12,8 +12,12 @@ class BaseCountService
Rails.cache.fetch(cache_key, cache_options) { uncached_count }.to_i Rails.cache.fetch(cache_key, cache_options) { uncached_count }.to_i
end end
def refresh_cache def count_stored?
Rails.cache.write(cache_key, uncached_count, raw: raw?) Rails.cache.read(cache_key).present?
end
def refresh_cache(&block)
Rails.cache.write(cache_key, block_given? ? yield : uncached_count, raw: raw?)
end end
def uncached_count def uncached_count
......
...@@ -2,7 +2,7 @@ module Clusters ...@@ -2,7 +2,7 @@ module Clusters
class CreateService < BaseService class CreateService < BaseService
attr_reader :access_token attr_reader :access_token
def execute(access_token) def execute(access_token = nil)
@access_token = access_token @access_token = access_token
raise Exception.new('Instance does not support multiple clusters') unless can_create_cluster? raise Exception.new('Instance does not support multiple clusters') unless can_create_cluster?
......
...@@ -98,6 +98,12 @@ module NotificationRecipientService ...@@ -98,6 +98,12 @@ module NotificationRecipientService
self << [target.participants(user), :participating] self << [target.participants(user), :participating]
end end
def add_mentions(user, target:)
return unless target.respond_to?(:mentioned_users)
self << [target.mentioned_users(user), :mention]
end
# Get project/group users with CUSTOM notification level # Get project/group users with CUSTOM notification level
def add_custom_notifications def add_custom_notifications
user_ids = [] user_ids = []
...@@ -227,6 +233,11 @@ module NotificationRecipientService ...@@ -227,6 +233,11 @@ module NotificationRecipientService
add_subscribed_users add_subscribed_users
if [:new_issue, :new_merge_request].include?(custom_action) if [:new_issue, :new_merge_request].include?(custom_action)
# These will all be participants as well, but adding with the :mention
# type ensures that users with the mention notification level will
# receive them, too.
add_mentions(current_user, target: target)
add_labels_subscribers add_labels_subscribers
end end
end end
...@@ -263,7 +274,7 @@ module NotificationRecipientService ...@@ -263,7 +274,7 @@ module NotificationRecipientService
def build! def build!
# Add all users participating in the thread (author, assignee, comment authors) # Add all users participating in the thread (author, assignee, comment authors)
add_participants(note.author) add_participants(note.author)
self << [note.mentioned_users, :mention] add_mentions(note.author, target: note)
unless note.for_personal_snippet? unless note.for_personal_snippet?
# Merge project watchers # Merge project watchers
......
# Service class for getting and caching the number of elements of several projects
# Warning: do not user this service with a really large set of projects
# because the service use maps to retrieve the project ids.
module Projects
class BatchCountService
def initialize(projects)
@projects = projects
end
def refresh_cache
@projects.each do |project|
service = count_service.new(project)
unless service.count_stored?
service.refresh_cache { global_count[project.id].to_i }
end
end
end
def project_ids
@projects.map(&:id)
end
def global_count(project)
raise NotImplementedError, 'global_count must be implemented and return an hash indexed by the project id'
end
def count_service
raise NotImplementedError, 'count_service must be implemented and return a Projects::CountService object'
end
end
end
# Service class for getting and caching the number of forks of several projects
# Warning: do not user this service with a really large set of projects
# because the service use maps to retrieve the project ids
module Projects
class BatchForksCountService < Projects::BatchCountService
def global_count
@global_count ||= begin
count_service.query(project_ids)
.group(:forked_from_project_id)
.count
end
end
def count_service
::Projects::ForksCountService
end
end
end
# Service class for getting and caching the number of issues of several projects
# Warning: do not user this service with a really large set of projects
# because the service use maps to retrieve the project ids
module Projects
class BatchOpenIssuesCountService < Projects::BatchCountService
def global_count
@global_count ||= begin
count_service.query(project_ids).group(:project_id).count
end
end
def count_service
::Projects::OpenIssuesCountService
end
end
end
...@@ -11,6 +11,10 @@ module Projects ...@@ -11,6 +11,10 @@ module Projects
@project = project @project = project
end end
def relation_for_count
self.class.query(@project.id)
end
def cache_key_name def cache_key_name
raise( raise(
NotImplementedError, NotImplementedError,
...@@ -21,5 +25,12 @@ module Projects ...@@ -21,5 +25,12 @@ module Projects
def cache_key def cache_key
['projects', 'count_service', VERSION, @project.id, cache_key_name] ['projects', 'count_service', VERSION, @project.id, cache_key_name]
end end
def self.query(project_ids)
raise(
NotImplementedError,
'"query" must be implemented and return an ActiveRecord::Relation'
)
end
end end
end end
module Projects module Projects
# Service class for getting and caching the number of forks of a project. # Service class for getting and caching the number of forks of a project.
class ForksCountService < Projects::CountService class ForksCountService < Projects::CountService
def relation_for_count
@project.forks
end
def cache_key_name def cache_key_name
'forks_count' 'forks_count'
end end
def self.query(project_ids)
# We can't directly change ForkedProjectLink to ForkNetworkMember here
# Nowadays, when a call using v3 to projects/:id/fork is made,
# the relationship to ForkNetworkMember is not updated
ForkedProjectLink.where(forked_from_project: project_ids)
end
end end
end end
...@@ -2,14 +2,14 @@ module Projects ...@@ -2,14 +2,14 @@ module Projects
# Service class for counting and caching the number of open issues of a # Service class for counting and caching the number of open issues of a
# project. # project.
class OpenIssuesCountService < Projects::CountService class OpenIssuesCountService < Projects::CountService
def relation_for_count
# We don't include confidential issues in this number since this would
# expose the number of confidential issues to non project members.
@project.issues.opened.public_only
end
def cache_key_name def cache_key_name
'open_issues_count' 'open_issues_count'
end end
def self.query(project_ids)
# We don't include confidential issues in this number since this would
# expose the number of confidential issues to non project members.
Issue.opened.public_only.where(project: project_ids)
end
end end
end end
...@@ -3,11 +3,7 @@ ...@@ -3,11 +3,7 @@
# Custom validator for ClusterName. # Custom validator for ClusterName.
class ClusterNameValidator < ActiveModel::EachValidator class ClusterNameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
if record.user? if record.managed?
unless value.present?
record.errors.add(attribute, " has to be present")
end
elsif record.gcp?
if record.persisted? && record.name_changed? if record.persisted? && record.name_changed?
record.errors.add(attribute, " can not be changed because it's synchronized with provider") record.errors.add(attribute, " can not be changed because it's synchronized with provider")
end end
...@@ -19,6 +15,10 @@ class ClusterNameValidator < ActiveModel::EachValidator ...@@ -19,6 +15,10 @@ class ClusterNameValidator < ActiveModel::EachValidator
unless value =~ Gitlab::Regex.kubernetes_namespace_regex unless value =~ Gitlab::Regex.kubernetes_namespace_regex
record.errors.add(attribute, Gitlab::Regex.kubernetes_namespace_regex_message) record.errors.add(attribute, Gitlab::Regex.kubernetes_namespace_regex_message)
end end
else
unless value.present?
record.errors.add(attribute, " has to be present")
end
end end
end end
end end
= render 'devise/shared/tab_single', tab_title:'Change your password' = render 'devise/shared/tab_single', tab_title:'Change your password'
.login-box .login-box
.login-body .login-body
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'gl-show-field-errors' }) do |f| = form_for(resource, as: resource_name, url: password_path(:user), html: { method: :put, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= devise_error_messages! = devise_error_messages!
= f.hidden_field :reset_password_token = f.hidden_field :reset_password_token
...@@ -17,5 +17,5 @@ ...@@ -17,5 +17,5 @@
.clearfix.prepend-top-20 .clearfix.prepend-top-20
%p %p
%span.light Didn't receive a confirmation email? %span.light Didn't receive a confirmation email?
= link_to "Request a new one", new_confirmation_path(resource_name) = link_to "Request a new one", new_confirmation_path(:user)
= render 'devise/shared/sign_in_link' = render 'devise/shared/sign_in_link'
...@@ -11,6 +11,6 @@ ...@@ -11,6 +11,6 @@
= f.check_box :remember_me, class: 'remember-me-checkbox' = f.check_box :remember_me, class: 'remember-me-checkbox'
%span Remember me %span Remember me
.pull-right.forgot-password .pull-right.forgot-password
= link_to "Forgot your password?", new_password_path(resource_name) = link_to "Forgot your password?", new_password_path(:user)
.submit-container.move-submit-down .submit-container.move-submit-down
= f.submit "Sign in", class: "btn btn-save" = f.submit "Sign in", class: "btn btn-save"
<%- if controller_name != 'sessions' %> <%- if controller_name != 'sessions' %>
<%= link_to "Sign in", new_session_path(resource_name), class: "btn" %><br /> <%= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: "btn" %><br />
<% end -%> <% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' && allow_signup? %> <%- if devise_mapping.registerable? && controller_name != 'registrations' && allow_signup? %>
<%= link_to "Sign up", new_registration_path(resource_name) %><br /> <%= link_to "Sign up", new_registration_path(:user) %><br />
<% end -%> <% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' %>
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "btn" %><br /> <%= link_to "Forgot your password?", new_password_path(:user), class: "btn" %><br />
<% end -%> <% end -%>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %><br /> <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(:user) %><br />
<% end -%> <% end -%>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %><br /> <%= link_to "Didn't receive unlock instructions?", new_unlock_path(:user) %><br />
<% end -%> <% end -%>
%p %p
%span.light %span.light
Already have login and password? Already have login and password?
= link_to "Sign in", new_session_path(resource_name) = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
...@@ -31,4 +31,4 @@ ...@@ -31,4 +31,4 @@
%p %p
%span.light Didn't receive a confirmation email? %span.light Didn't receive a confirmation email?
= succeed '.' do = succeed '.' do
= link_to "Request a new one", new_confirmation_path(resource_name) = link_to "Request a new one", new_confirmation_path(:user)
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%p Try logging in using your username or email. If you have forgotten your password, try recovering it %p Try logging in using your username or email. If you have forgotten your password, try recovering it
= link_to "Sign in", new_session_path(:user), class: 'btn primary' = link_to "Sign in", new_session_path(:user), class: 'btn primary'
= link_to "Recover password", new_password_path(resource_name), class: 'btn secondary' = link_to "Recover password", new_password_path(:user), class: 'btn secondary'
%hr %hr
%p.light If none of the options work, try contacting a GitLab administrator. %p.light If none of the options work, try contacting a GitLab administrator.
...@@ -19,5 +19,5 @@ ...@@ -19,5 +19,5 @@
distributed with computer software, forming part of its documentation. distributed with computer software, forming part of its documentation.
%p %p
We recommend you to We recommend you to
= link_to "add a README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link' = link_to "add a README", add_special_file_path(@project, file_name: 'README.md')
file to the repository and GitLab will render it here instead of this message. file to the repository and GitLab will render it here instead of this message.
...@@ -3,6 +3,6 @@ ...@@ -3,6 +3,6 @@
%tr.tree-item{ 'data-link' => path_to_directory } %tr.tree-item{ 'data-link' => path_to_directory }
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon('folder', '755', directory.name) = tree_icon('folder', '755', directory.name)
= link_to path_to_directory do = link_to path_to_directory, class: 'str-truncated' do
%span.str-truncated= directory.name %span= directory.name
%td %td
...@@ -6,12 +6,12 @@ ...@@ -6,12 +6,12 @@
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon('file', blob.mode, blob.name) = tree_icon('file', blob.mode, blob.name)
- if external_link - if external_link
= link_to path_to_file, class: 'tree-item-file-external-link js-artifact-tree-tooltip', = link_to path_to_file, class: 'tree-item-file-external-link js-artifact-tree-tooltip str-truncated',
target: '_blank', rel: 'noopener noreferrer', title: _('Opens in a new window') do target: '_blank', rel: 'noopener noreferrer', title: _('Opens in a new window') do
%span.str-truncated>= blob.name %span>= blob.name
= icon('external-link', class: 'js-artifact-tree-external-icon') = icon('external-link', class: 'js-artifact-tree-external-icon')
- else - else
= link_to path_to_file do = link_to path_to_file, class: 'str-truncated' do
%span.str-truncated= blob.name %span= blob.name
%td %td
= number_to_human_size(blob.size, precision: 2) = number_to_human_size(blob.size, precision: 2)
...@@ -8,8 +8,7 @@ ...@@ -8,8 +8,7 @@
%li{ class: "js-branch-#{branch.name}" } %li{ class: "js-branch-#{branch.name}" }
%div %div
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated ref-name' do
= icon('code-fork') = icon('code-fork', class: 'append-right-5') + "#{branch.name}"
= branch.name
&nbsp; &nbsp;
- if branch.name == @repository.root_ref - if branch.name == @repository.root_ref
%span.label.label-primary default %span.label.label-primary default
......
...@@ -41,6 +41,6 @@ ...@@ -41,6 +41,6 @@
- if commit.status(ref) - if commit.status(ref)
= render_commit_status(commit, ref: ref) = render_commit_status(commit, ref: ref)
= link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent" = link_to commit.short_id, project_commit_path(project, commit), class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard")) = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit) = link_to_browse_code(project, commit)
...@@ -14,12 +14,12 @@ ...@@ -14,12 +14,12 @@
%p %p
Otherwise you can start with adding a Otherwise you can start with adding a
= succeed ',' do = succeed ',' do
= link_to "README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link' = link_to "README", add_special_file_path(@project, file_name: 'README.md')
a a
= succeed ',' do = succeed ',' do
= link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link' = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE')
or a or a
= link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore'), class: 'underlined-link' = link_to '.gitignore', add_special_file_path(@project, file_name: '.gitignore')
to this project. to this project.
%p %p
You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected. You will need to be owner or have the master permission level for the initial push, as the master branch is automatically protected.
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%li prevent pushes from everybody except Masters %li prevent pushes from everybody except Masters
%li prevent <strong>anyone</strong> from force pushing to the branch %li prevent <strong>anyone</strong> from force pushing to the branch
%li prevent <strong>anyone</strong> from deleting the branch %li prevent <strong>anyone</strong> from deleting the branch
%p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}. %p Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches")} and #{link_to "project permissions", help_page_path("user/permissions")}.
- if can? current_user, :admin_project, @project - if can? current_user, :admin_project, @project
= content_for :create_protected_branch = content_for :create_protected_branch
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%li Prevent <strong>anyone</strong> from updating the tag %li Prevent <strong>anyone</strong> from updating the tag
%li Prevent <strong>anyone</strong> from deleting the tag %li Prevent <strong>anyone</strong> from deleting the tag
%p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags"), class: "underlined-link"}. %p Read more about #{link_to "protected tags", help_page_path("user/project/protected_tags")}.
- if can? current_user, :admin_project, @project - if can? current_user, :admin_project, @project
= yield :create_protected_tag = yield :create_protected_tag
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon(type, blob_item.mode, blob_item.name) = tree_icon(type, blob_item.mode, blob_item.name)
- file_name = blob_item.name - file_name = blob_item.name
= link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), title: file_name do = link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), class: 'str-truncated', title: file_name do
%span.str-truncated= file_name %span= file_name
%td.hidden-xs.tree-commit %td.hidden-xs.tree-commit
%td.tree-time-ago.cgray.text-right %td.tree-time-ago.cgray.text-right
= render 'projects/tree/spinner' = render 'projects/tree/spinner'
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon(type, tree_item.mode, tree_item.name) = tree_icon(type, tree_item.mode, tree_item.name)
- path = flatten_tree(@path, tree_item) - path = flatten_tree(@path, tree_item)
= link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path do = link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), class: 'str-truncated', title: path do
%span.str-truncated= path %span= path
%td.hidden-xs.tree-commit %td.hidden-xs.tree-commit
%td.tree-time-ago.text-right %td.tree-time-ago.text-right
= render 'projects/tree/spinner' = render 'projects/tree/spinner'
...@@ -6,9 +6,8 @@ ...@@ -6,9 +6,8 @@
- git_access_url = project_wikis_git_access_path(@project) - git_access_url = project_wikis_git_access_path(@project)
= link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do
= succeed '&nbsp;' do = icon('cloud-download', class: 'append-right-5')
= icon('cloud-download') %span= _("Clone repository")
= _("Clone repository")
.blocks-container .blocks-container
.block.block-first .block.block-first
......
...@@ -38,9 +38,9 @@ ...@@ -38,9 +38,9 @@
= 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?'}
.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') do = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action btn-link') do
view merge requests view merge requests
= link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action btn-link') do
view open issues view open issues
- if current_user - if current_user
......
- type = local_assigns.fetch(:type, :issues) - type = local_assigns.fetch(:type, :issues)
- page_context_word = type.to_s.humanize(capitalize: false) - page_context_word = type.to_s.humanize(capitalize: false)
- issuables = @issues || @merge_requests
%ul.nav-links.issues-state-filters %ul.nav-links.issues-state-filters
%li{ class: active_when(params[:state] == 'opened') }> %li{ class: active_when(params[:state] == 'opened') }>
...@@ -20,6 +19,4 @@ ...@@ -20,6 +19,4 @@
= link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed' } do = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by issues that are currently closed.', data: { state: 'closed' } do
#{issuables_state_counter_text(type, :closed)} #{issuables_state_counter_text(type, :closed)}
%li{ class: active_when(params[:state] == 'all') }> = render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all)
= link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}.", data: { state: 'all' } do
#{issuables_state_counter_text(type, :all)}
- page_context_word = local_assigns.fetch(:page_context_word)
- counter = local_assigns.fetch(:counter)
%li{ class: active_when(params[:state] == 'all') }>
= link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}.", data: { state: 'all' } do
#{counter}
...@@ -31,8 +31,7 @@ ...@@ -31,8 +31,7 @@
.note-header .note-header
.note-header-info .note-header-info
%a{ href: user_path(note.author) } %a{ href: user_path(note.author) }
%span.note-header-author-name %span.note-header-author-name= sanitize(note.author.name)
= sanitize(note.author.name)
%span.note-headline-light %span.note-headline-light
= note.author.to_reference = note.author.to_reference
%span.note-headline-light %span.note-headline-light
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
.cover-desc .cover-desc
- unless @user.public_email.blank? - unless @user.public_email.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider
= link_to @user.public_email, "mailto:#{@user.public_email}" = link_to @user.public_email, "mailto:#{@user.public_email}", class: 'text-link'
- unless @user.skype.blank? - unless @user.skype.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider
= link_to "skype:#{@user.skype}", title: "Skype" do = link_to "skype:#{@user.skype}", title: "Skype" do
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
= icon('twitter-square') = icon('twitter-square')
- unless @user.website_url.blank? - unless @user.website_url.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider
= link_to @user.short_website_url, @user.full_website_url = link_to @user.short_website_url, @user.full_website_url, class: 'text-link'
- unless @user.location.blank? - unless @user.location.blank?
.profile-link-holder.middle-dot-divider .profile-link-holder.middle-dot-divider
= icon('map-marker') = icon('map-marker')
......
...@@ -32,16 +32,14 @@ module RepositoryCheck ...@@ -32,16 +32,14 @@ module RepositoryCheck
end end
def git_fsck(repository) def git_fsck(repository)
path = repository.path_to_repo return false unless repository.exists?
cmd = %W(nice git --git-dir=#{path} fsck)
output, status = Gitlab::Popen.popen(cmd)
if status.zero? repository.raw_repository.fsck
true
else true
Gitlab::RepositoryCheckLogger.error("command failed: #{cmd.join(' ')}\n#{output}") rescue Gitlab::Git::Repository::GitError => e
false Gitlab::RepositoryCheckLogger.error(e.message)
end false
end end
def has_pushes?(project) def has_pushes?(project)
......
...@@ -8,18 +8,18 @@ class RepositoryForkWorker ...@@ -8,18 +8,18 @@ class RepositoryForkWorker
sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION sidekiq_options status_expiration: StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION
def perform(project_id, forked_from_repository_storage_path, source_path, target_path) def perform(project_id, forked_from_repository_storage_path, source_disk_path)
project = Project.find(project_id) project = Project.find(project_id)
return unless start_fork(project) return unless start_fork(project)
Gitlab::Metrics.add_event(:fork_repository, Gitlab::Metrics.add_event(:fork_repository,
source_path: source_path, source_path: source_disk_path,
target_path: target_path) target_path: project.disk_path)
result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_path, result = gitlab_shell.fork_repository(forked_from_repository_storage_path, source_disk_path,
project.repository_storage_path, target_path) project.repository_storage_path, project.disk_path)
raise ForkError, "Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}" unless result raise ForkError, "Unable to fork project #{project_id} for repository #{source_disk_path} -> #{project.disk_path}" unless result
project.repository.after_import project.repository.after_import
raise ForkError, "Project #{project_id} had an invalid repository after fork" unless project.valid_repo? raise ForkError, "Project #{project_id} had an invalid repository after fork" unless project.valid_repo?
......
---
title: Create a new form to add Existing Kubernetes Cluster
merge_request: 14805
author:
type: added
---
title: Fix sending notification emails to users with the mention level set who were
mentioned in an issue or merge request description
merge_request:
author:
type: fixed
---
title: Confirming email with invalid token should no longer generate an error
merge_request: 15726
author:
type: fixed
---
title: Add axios to common file
merge_request:
author:
type: performance
---
title: show status of gitlab reference links in wiki
merge_request: 15694
author: haseebeqx
type: added
---
title: Fix the fork project functionality for projects with hashed storage
merge_request: 15671
author:
type: fixed
---
title: Fix typo in docs about Elasticsearch
merge_request: 15699
author: Takuya Noguchi
type: other
---
title: Keep track of all circuitbreaker keys in a set
merge_request: 15613
author:
type: performance
---
title: Reduce requests for project forks on show page of projects that have forks
merge_request: 15663
author:
type: performance
---
title: Added default order to UsersFinder
merge_request: 15679
author:
type: fixed
---
title: Optimize API /groups/:id/projects by preloading associations
merge_request:
author:
type: performance
---
title: Use custom user agent header in all GCP API requests.
merge_request: 15705
author:
type: changed
# WARNING changes in this file must be manually propagated to gitaly-ruby.
#
# https://gitlab.com/gitlab-org/gitaly/blob/master/ruby/lib/gitlab/gollum.rb
module Gollum module Gollum
GIT_ADAPTER = "rugged".freeze GIT_ADAPTER = "rugged".freeze
end end
......
require './spec/support/sidekiq'
Gitlab::Seeder.quiet do
User.seed do |s|
s.id = 1
s.name = 'Administrator'
s.email = 'admin@example.com'
s.notification_email = 'admin@example.com'
s.username = 'root'
s.password = '5iveL!fe'
s.admin = true
s.projects_limit = 100
s.confirmed_at = DateTime.now
end
end
...@@ -80,7 +80,7 @@ changes. ...@@ -80,7 +80,7 @@ changes.
The first example focuses on _how_ we fixed something, not on _what_ it fixes. The first example focuses on _how_ we fixed something, not on _what_ it fixes.
The rewritten version clearly describes the _end benefit_ to the user (fewer 500 The rewritten version clearly describes the _end benefit_ to the user (fewer 500
errors), and _when_ (searching commits with ElasticSearch). errors), and _when_ (searching commits with Elasticsearch).
Use your best judgement and try to put yourself in the mindset of someone Use your best judgement and try to put yourself in the mindset of someone
reading the compiled changelog. Does this entry add value? Does it offer context reading the compiled changelog. Does this entry add value? Does it offer context
......
...@@ -170,12 +170,6 @@ You can combine one or more of the following: ...@@ -170,12 +170,6 @@ You can combine one or more of the following:
= link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info' = link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info'
``` ```
1. **Underlining a link.**
```haml
= link_to 'Help page', help_page_path('user/permissions'), class: 'underlined-link'
```
1. **Using links inline of some text.** 1. **Using links inline of some text.**
```haml ```haml
......
...@@ -152,12 +152,23 @@ CE and EE. ...@@ -152,12 +152,23 @@ CE and EE.
## Previewing the changes live ## Previewing the changes live
If you want to preview the doc changes of your merge request live, you can use If you want to preview the doc changes of your merge request live, you can use
the manual `review-docs-deploy` job in your merge request. the manual `review-docs-deploy` job in your merge request. You will need at
least Master permissions to be able to run it and is currently enabled for the
following projects:
- https://gitlab.com/gitlab-org/gitlab-ce
- https://gitlab.com/gitlab-org/gitlab-ee
NOTE: **Note:**
You will need to push a branch to those repositories, it doesn't work for forks.
TIP: **Tip:** TIP: **Tip:**
If your branch contains only documentation changes, you can use If your branch contains only documentation changes, you can use
[special branch names](#testing) to avoid long running pipelines. [special branch names](#testing) to avoid long running pipelines.
In the mini pipeline graph, you should see an `>>` icon. Clicking on it will
reveal the `review-docs-deploy` job. Hit the play button for the job to start.
![Manual trigger a docs build](img/manual_build_docs.png) ![Manual trigger a docs build](img/manual_build_docs.png)
This job will: This job will:
......
...@@ -175,7 +175,7 @@ A [feature](https://docs.gitlab.com/ce/user/project/container_registry.html) of ...@@ -175,7 +175,7 @@ A [feature](https://docs.gitlab.com/ce/user/project/container_registry.html) of
### EC2 Instance ### EC2 Instance
### ElasticSearch ### Elasticsearch
Elasticsearch is a flexible, scalable and powerful search service. When [enabled](https://gitlab.com/help/integration/elasticsearch.md), it helps keep GitLab's search fast when dealing with a huge amount of data. Elasticsearch is a flexible, scalable and powerful search service. When [enabled](https://gitlab.com/help/integration/elasticsearch.md), it helps keep GitLab's search fast when dealing with a huge amount of data.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment