diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2016-12-10 01:27:56 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2016-12-10 01:27:56 +0800 |
commit | e0df1b5f9944cff1ee7234c9fa292a7a06456c08 (patch) | |
tree | bd3e760fb41f1974bd6aac0ccefbc6f97cdce04f /app | |
parent | 3e01385bca92dc8c0df3aa4032cc58d708dc0ff5 (diff) | |
parent | ad4c2a08104cba0557d824fac6a70eedd45921b7 (diff) | |
download | gitlab-ce-e0df1b5f9944cff1ee7234c9fa292a7a06456c08.tar.gz |
Merge remote-tracking branch 'upstream/master' into fix-git-hooks-when-creating-file
* upstream/master: (60 commits)
Refactor SSH keys docs
Improvements to setting up ssh
Do not reload diff for merge request made from fork when target branch in fork is updated
Add 8.12.10, 8.12.11, and 8.12.12 CHANGELOG.md items
Changes after review
Fix broken test
Adds CHANGELOG entry
Adds tests
Uniformize props name format
Replace commit icon svg logic
Replace play icon svg logic
Update docs to reflect new defaults on omnibus
Merge branch 'jej-23867-use-mr-finder-instead-of-access-check' into 'security'
Merge branch 'html-safe-diff-line-content' into 'security'
Merge branch 'rs-filter-authentication_token' into 'security'
Merge branch 'destroy-session' into 'security'
remove unnecessary issues event filter on comments tab
Updating reference to database password
Add CHANGELOG entry
Displays milestone remaining days only when it's present
...
Diffstat (limited to 'app')
78 files changed, 565 insertions, 363 deletions
diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 index 5434a19bcec..0ff5c0fab05 100644 --- a/app/assets/javascripts/blob/template_selector.js.es6 +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -70,6 +70,8 @@ // e.g. // Api.gitignoreText item.name, @requestFileSuccess.bind(@) requestFileSuccess(file, { skipFocus } = {}) { + if (!file) return; + const oldValue = this.editor.getValue(); let newValue = file.content; diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 3a7c5ff3681..413117c2226 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -24,6 +24,7 @@ switch (page) { case 'sessions:new': new UsernameValidator(); + new ActiveTabMemoizer(); break; case 'projects:boards:show': case 'projects:boards:index': diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 84faabf938a..1db29dd47fb 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -74,6 +74,8 @@ projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath, newEnvironmentPath: environmentsData.newEnvironmentPath, helpPagePath: environmentsData.helpPagePath, + commitIconSvg: environmentsData.commitIconSvg, + playIconSvg: environmentsData.playIconSvg, }; }, @@ -227,7 +229,9 @@ :model="model" :toggleRow="toggleRow.bind(model)" :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed"></tr> + :can-read-environment="canReadEnvironmentParsed" + :play-icon-svg="playIconSvg" + :commit-icon-svg="commitIconSvg"></tr> <tr v-if="model.isOpen && model.children && model.children.length > 0" is="environment-item" @@ -235,7 +239,9 @@ :model="children" :toggleRow="toggleRow.bind(children)" :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed"> + :can-read-environment="canReadEnvironmentParsed" + :play-icon-svg="playIconSvg" + :commit-icon-svg="commitIconSvg"> </tr> </template> diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6 index d149a446e0b..7c743705d51 100644 --- a/app/assets/javascripts/environments/components/environment_actions.js.es6 +++ b/app/assets/javascripts/environments/components/environment_actions.js.es6 @@ -12,38 +12,18 @@ required: false, default: () => [], }, - }, - - /** - * Appends the svg icon that were render in the index page. - * In order to reuse the svg instead of copy and paste in this template - * we need to render it outside this component using =custom_icon partial. - * - * TODO: Remove this when webpack is merged. - * - */ - mounted() { - const playIcon = document.querySelector('.play-icon-svg.hidden svg'); - - const dropdownContainer = this.$el.querySelector('.dropdown-play-icon-container'); - const actionContainers = this.$el.querySelectorAll('.action-play-icon-container'); - // Phantomjs does not have support to iterate a nodelist. - const actionsArray = [].slice.call(actionContainers); - - if (playIcon && actionsArray && dropdownContainer) { - dropdownContainer.appendChild(playIcon.cloneNode(true)); - actionsArray.forEach((element) => { - element.appendChild(playIcon.cloneNode(true)); - }); - } + playIconSvg: { + type: String, + required: false, + }, }, template: ` <div class="inline"> <div class="dropdown"> <a class="dropdown-new btn btn-default" data-toggle="dropdown"> - <span class="dropdown-play-icon-container"></span> + <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span> <i class="fa fa-caret-down"></i> </a> @@ -53,7 +33,9 @@ data-method="post" rel="nofollow" class="js-manual-action-link"> - <span class="action-play-icon-container"></span> + + <span class="js-action-play-icon-container" v-html="playIconSvg"></span> + <span> {{action.name}} </span> diff --git a/app/assets/javascripts/environments/components/environment_external_url.js.es6 b/app/assets/javascripts/environments/components/environment_external_url.js.es6 index 79cd5ded5bd..aed65b33c04 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.js.es6 +++ b/app/assets/javascripts/environments/components/environment_external_url.js.es6 @@ -7,14 +7,14 @@ window.gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', { props: { - external_url: { + externalUrl: { type: String, default: '', }, }, template: ` - <a class="btn external_url" :href="external_url" target="_blank"> + <a class="btn external_url" :href="externalUrl" target="_blank"> <i class="fa fa-external-link"></i> </a> `, diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 6ed14261fc3..2e046a60146 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -58,6 +58,16 @@ required: false, default: false, }, + + commitIconSvg: { + type: String, + required: false, + }, + + playIconSvg: { + type: String, + required: false, + }, }, data() { @@ -451,11 +461,12 @@ <div v-if="!isFolder && hasLastDeploymentKey" class="js-commit-component"> <commit-component :tag="commitTag" - :commit_ref="commitRef" - :commit_url="commitUrl" - :short_sha="commitShortSha" + :commit-ref="commitRef" + :commit-url="commitUrl" + :short-sha="commitShortSha" :title="commitTitle" - :author="commitAuthor"> + :author="commitAuthor" + :commit-icon-svg="commitIconSvg"> </commit-component> </div> <p v-if="!isFolder && !hasLastDeploymentKey" class="commit-title"> @@ -476,6 +487,7 @@ <div v-if="hasManualActions && canCreateDeployment" class="inline js-manual-actions-container"> <actions-component + :play-icon-svg="playIconSvg" :actions="manualActions"> </actions-component> </div> @@ -483,22 +495,22 @@ <div v-if="model.external_url && canReadEnvironment" class="inline js-external-url-container"> <external-url-component - :external_url="model.external_url"> - </external_url-component> + :external-url="model.external_url"> + </external-url-component> </div> <div v-if="isStoppable && canCreateDeployment" class="inline js-stop-component-container"> <stop-component - :stop_url="model.stop_path"> + :stop-url="model.stop_path"> </stop-component> </div> <div v-if="canRetry && canCreateDeployment" class="inline js-rollback-component-container"> <rollback-component - :is_last_deployment="isLastDeployment" - :retry_url="retryUrl"> + :is-last-deployment="isLastDeployment" + :retry-url="retryUrl"> </rollback-component> </div> </div> diff --git a/app/assets/javascripts/environments/components/environment_rollback.js.es6 b/app/assets/javascripts/environments/components/environment_rollback.js.es6 index 55e5c826e07..6d4e8fad604 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.js.es6 +++ b/app/assets/javascripts/environments/components/environment_rollback.js.es6 @@ -7,19 +7,20 @@ window.gl.environmentsList.RollbackComponent = Vue.component('rollback-component', { props: { - retry_url: { + retryUrl: { type: String, default: '', }, - is_last_deployment: { + + isLastDeployment: { type: Boolean, default: true, }, }, template: ` - <a class="btn" :href="retry_url" data-method="post" rel="nofollow"> - <span v-if="is_last_deployment"> + <a class="btn" :href="retryUrl" data-method="post" rel="nofollow"> + <span v-if="isLastDeployment"> Re-deploy </span> <span v-else> diff --git a/app/assets/javascripts/environments/components/environment_stop.js.es6 b/app/assets/javascripts/environments/components/environment_stop.js.es6 index e6d66a0148c..7292f924e5c 100644 --- a/app/assets/javascripts/environments/components/environment_stop.js.es6 +++ b/app/assets/javascripts/environments/components/environment_stop.js.es6 @@ -7,7 +7,7 @@ window.gl.environmentsList.StopComponent = Vue.component('stop-component', { props: { - stop_url: { + stopUrl: { type: String, default: '', }, @@ -15,7 +15,7 @@ template: ` <a class="btn stop-env-link" - :href="stop_url" + :href="stopUrl" data-confirm="Are you sure you want to stop this environment?" data-method="post" rel="nofollow"> diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 963d2851e5f..e8e502694d6 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -29,7 +29,7 @@ setTimeago = true; } - $timeagoEls.each(function() { + $timeagoEls.filter(':not([data-timeago-rendered])').each(function() { var $el = $(this); $el.attr('title', gl.utils.formatDate($el.attr('datetime'))); @@ -39,6 +39,8 @@ template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>' }); } + + $el.attr('data-timeago-rendered', true); gl.utils.renderTimeago($el); }); }; diff --git a/app/assets/javascripts/signin_tabs_memoizer.js.es6 b/app/assets/javascripts/signin_tabs_memoizer.js.es6 new file mode 100644 index 00000000000..d811d1cd53a --- /dev/null +++ b/app/assets/javascripts/signin_tabs_memoizer.js.es6 @@ -0,0 +1,49 @@ +/* eslint no-param-reassign: ["error", { "props": false }]*/ +/* eslint no-new: "off" */ +((global) => { + /** + * Memorize the last selected tab after reloading a page. + * Does that setting the current selected tab in the localStorage + */ + class ActiveTabMemoizer { + constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) { + this.currentTabKey = currentTabKey; + this.tabSelector = tabSelector; + this.bootstrap(); + } + + bootstrap() { + const tabs = document.querySelectorAll(this.tabSelector); + if (tabs.length > 0) { + tabs[0].addEventListener('click', (e) => { + if (e.target && e.target.nodeName === 'A') { + const anchorName = e.target.getAttribute('href'); + this.saveData(anchorName); + } + }); + } + + this.showTab(); + } + + showTab() { + const anchorName = this.readData(); + if (anchorName) { + const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`); + if (tab) { + tab.click(); + } + } + } + + saveData(val) { + localStorage.setItem(this.currentTabKey, val); + } + + readData() { + return localStorage.getItem(this.currentTabKey); + } + } + + global.ActiveTabMemoizer = ActiveTabMemoizer; +})(window); diff --git a/app/assets/javascripts/vue_common_component/commit.js.es6 b/app/assets/javascripts/vue_common_component/commit.js.es6 index 2ef2959cbf4..62a22e39a3b 100644 --- a/app/assets/javascripts/vue_common_component/commit.js.es6 +++ b/app/assets/javascripts/vue_common_component/commit.js.es6 @@ -23,7 +23,7 @@ * name * ref_url */ - commit_ref: { + commitRef: { type: Object, required: false, default: () => ({}), @@ -32,16 +32,16 @@ /** * Used to link to the commit sha. */ - commit_url: { + commitUrl: { type: String, required: false, default: '', }, /** - * Used to show the commit short_sha that links to the commit url. + * Used to show the commit short sha that links to the commit url. */ - short_sha: { + shortSha: { type: String, required: false, default: '', @@ -68,6 +68,11 @@ required: false, default: () => ({}), }, + + commitIconSvg: { + type: String, + required: false, + }, }, computed: { @@ -80,7 +85,7 @@ * @returns {Boolean} */ hasCommitRef() { - return this.commit_ref && this.commit_ref.name && this.commit_ref.ref_url; + return this.commitRef && this.commitRef.name && this.commitRef.ref_url; }, /** @@ -110,24 +115,6 @@ }, }, - /** - * In order to reuse the svg instead of copy and paste in this template - * we need to render it outside this component using =custom_icon partial. - * Make sure it has this structure: - * .commit-icon-svg.hidden - * svg - * - * TODO: Find a better way to include SVG - */ - mounted() { - const commitIconContainer = this.$el.querySelector('.commit-icon-container'); - const commitIcon = document.querySelector('.commit-icon-svg.hidden svg'); - - if (commitIconContainer && commitIcon) { - commitIconContainer.appendChild(commitIcon.cloneNode(true)); - } - }, - template: ` <div class="branch-commit"> @@ -138,15 +125,15 @@ <a v-if="hasCommitRef" class="monospace branch-name" - :href="commit_ref.ref_url"> - {{commit_ref.name}} + :href="commitRef.ref_url"> + {{commitRef.name}} </a> - <div class="icon-container commit-icon commit-icon-container"></div> + <div v-html="commitIconSvg" class="commit-icon js-commit-icon"></div> <a class="commit-id monospace" - :href="commit_url"> - {{short_sha}} + :href="commitUrl"> + {{shortSha}} </a> <p class="commit-title"> @@ -162,7 +149,7 @@ </a> <a class="commit-row-message" - :href="commit_url"> + :href="commitUrl"> {{title}} </a> </span> diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 4d4835568ed..c82a9a2b9e3 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -6,6 +6,7 @@ @import "framework/animations.scss"; @import "framework/avatar.scss"; +@import "framework/asciidoctor.scss"; @import "framework/blocks.scss"; @import "framework/buttons.scss"; @import "framework/calendar.scss"; diff --git a/app/assets/stylesheets/framework/asciidoctor.scss b/app/assets/stylesheets/framework/asciidoctor.scss new file mode 100644 index 00000000000..62493c32833 --- /dev/null +++ b/app/assets/stylesheets/framework/asciidoctor.scss @@ -0,0 +1,27 @@ +.admonitionblock td.icon { + width: 1%; + + [class^="fa icon-"] { + @extend .fa-2x; + } + + .icon-note { + @extend .fa-thumb-tack; + } + + .icon-tip { + @extend .fa-lightbulb-o; + } + + .icon-warning { + @extend .fa-exclamation-triangle; + } + + .icon-caution { + @extend .fa-fire; + } + + .icon-important { + @extend .fa-exclamation-circle; + } +} diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss index c13cb4a02b2..dece5c3202b 100644 --- a/app/assets/stylesheets/framework/awards.scss +++ b/app/assets/stylesheets/framework/awards.scss @@ -1,7 +1,7 @@ .awards { .emoji-icon { - width: 19px; - height: 19px; + width: 20px; + height: 20px; } } @@ -136,5 +136,6 @@ .award-control-icon { color: $award-emoji-new-btn-icon-color; + margin-top: 1px; } } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 600bf17259b..251e43d2edd 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -255,6 +255,7 @@ img.emoji { height: 20px; vertical-align: top; width: 20px; + margin-top: 1px; } .chart { diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 69da520f21f..ea77348633d 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -71,6 +71,10 @@ border-bottom: 2px solid $link-underline-blue; color: $black; font-weight: 600; + + .badge { + color: $black; + } } .badge { diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 16b099c09eb..10eb3d4203e 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -124,7 +124,7 @@ ul.notes { position: absolute; left: 0; bottom: 0; - background: linear-gradient(rgba($gray-light, 0.1) -100px, $white-light 100%); + background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%); } &.hide-shade { @@ -413,7 +413,6 @@ ul.notes { .fa { color: $notes-action-color; position: relative; - top: 1px; font-size: 17px; } diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index aa7570cd896..1e3d194e9f9 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -56,7 +56,7 @@ class Admin::GroupsController < Admin::ApplicationController private def group - @group ||= Group.find_by(path: params[:id]) + @group ||= Group.find_by_full_path(params[:id]) end def group_params diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 5d11f286e9a..d2fcb2efd6a 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -82,10 +82,8 @@ module CreatesCommit def merge_request_exists? return @merge_request if defined?(@merge_request) - @merge_request = @mr_target_project.merge_requests.opened.find_by( - source_branch: @mr_source_branch, - target_branch: @mr_target_branch - ) + @merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened. + find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch) end def different_project? diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb index 949b4a6c25a..c411c21bb80 100644 --- a/app/controllers/groups/application_controller.rb +++ b/app/controllers/groups/application_controller.rb @@ -9,7 +9,7 @@ class Groups::ApplicationController < ApplicationController def group unless @group id = params[:group_id] || params[:id] - @group = Group.find_by(path: id) + @group = Group.find_by_full_path(id) unless @group && can?(current_user, :read_group, @group) @group = nil diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index cdfc1ba7b92..8197d9e4c99 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -65,7 +65,7 @@ class Projects::CommitController < Projects::ApplicationController return render_404 if @target_branch.blank? - create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.", + create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.", success_path: successful_change_path, failure_path: failed_change_path) end @@ -74,26 +74,24 @@ class Projects::CommitController < Projects::ApplicationController return render_404 if @target_branch.blank? - create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.", + create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.", success_path: successful_change_path, failure_path: failed_change_path) end private def successful_change_path - return referenced_merge_request_url if @commit.merged_merge_request - - namespace_project_commits_url(@project.namespace, @project, @target_branch) + referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch) end def failed_change_path - return referenced_merge_request_url if @commit.merged_merge_request - - namespace_project_commit_url(@project.namespace, @project, params[:id]) + referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id]) end def referenced_merge_request_url - namespace_project_merge_request_url(@project.namespace, @project, @commit.merged_merge_request) + if merge_request = @commit.merged_merge_request(current_user) + namespace_project_merge_request_url(@project.namespace, @project, merge_request) + end end def commit diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index aba87b6144b..ad92f05a42d 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -21,7 +21,7 @@ class Projects::CommitsController < Projects::ApplicationController @note_counts = project.notes.where(commit_id: @commits.map(&:id)). group(:commit_id).count - @merge_request = @project.merge_requests.opened. + @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened. find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref) respond_to do |format| diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index e2b178314c0..325987199fa 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -54,7 +54,7 @@ class Projects::CompareController < Projects::ApplicationController end def merge_request - @merge_request ||= @project.merge_requests.opened. + @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened. find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref) end end diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb index 148e39630e3..1349b015a63 100644 --- a/app/controllers/projects/discussions_controller.rb +++ b/app/controllers/projects/discussions_controller.rb @@ -24,7 +24,7 @@ class Projects::DiscussionsController < Projects::ApplicationController private def merge_request - @merge_request ||= @project.merge_requests.find_by!(iid: params[:merge_request_id]) + @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).find_by!(iid: params[:merge_request_id]) end def discussion diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 699a56ae2f8..3fb8bba3cd0 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,14 +10,38 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_members = @project.project_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) + group = @project.group + + if group + # We need `.where.not(user_id: nil)` here otherwise when a group has an + # invitee, it would make the following query return 0 rows since a NULL + # user_id would be present in the subquery + # See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values + # FIXME: This whole logic should be moved to a finder! + non_null_user_ids = @project_members.where.not(user_id: nil).select(:user_id) + group_members = group.group_members.where.not(user_id: non_null_user_ids) + group_members = group_members.non_invite unless can?(current_user, :admin_group, @group) + end + if params[:search].present? - users = @project.users.search(params[:search]).to_a - @project_members = @project_members.where(user_id: users) + user_ids = @project.users.search(params[:search]).select(:id) + @project_members = @project_members.where(user_id: user_ids) + + if group_members + user_ids = group.users.search(params[:search]).select(:id) + group_members = group_members.where(user_id: user_ids) + end @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) end - @project_members = @project_members.order(access_level: :desc).page(params[:page]) + member_ids = @project_members.pluck(:id) + + if group_members + member_ids += group_members.pluck(:id) + end + + @project_members = Member.where(id: member_ids).order(access_level: :desc).page(params[:page]) @requesters = AccessRequestsFinder.new(@project).execute(current_user) diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb index 52517381c65..a41fcb85c40 100644 --- a/app/controllers/projects/todos_controller.rb +++ b/app/controllers/projects/todos_controller.rb @@ -18,7 +18,7 @@ class Projects::TodosController < Projects::ApplicationController when "issue" IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id]) when "merge_request" - @project.merge_requests.find(params[:issuable_id]) + MergeRequestsFinder.new(current_user, project_id: @project.id).find(params[:issuable_id]) end end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 3327f4f2b87..c45196cc3e9 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -27,7 +27,10 @@ class RegistrationsController < Devise::RegistrationsController DeleteUserService.new(current_user).execute(current_user) respond_to do |format| - format.html { redirect_to new_user_session_path, notice: "Account successfully removed." } + format.html do + session.try(:destroy) + redirect_to new_user_session_path, notice: "Account successfully removed." + end end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 5d7ecfeacf4..38e7c6f4a48 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -31,6 +31,8 @@ class SessionsController < Devise::SessionsController resource.update_attributes(reset_password_token: nil, reset_password_sent_at: nil) end + # hide the signed-in notification + flash[:notice] = nil log_audit_event(current_user, with: authentication_method) end end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index c9bee01b9ad..b4c14d05eaf 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -77,6 +77,10 @@ class IssuableFinder counts end + def find_by!(*params) + execute.find_by!(*params) + end + def group return @group if defined?(@group) diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index a653a6d59c6..2484339e3a4 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -14,7 +14,7 @@ class NotesFinder when "issue" IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author when "merge_request" - project.merge_requests.find(target_id).mr_and_commit_notes.inc_author + MergeRequestsFinder.new(current_user, project_id: project.id).find(target_id).mr_and_commit_notes.inc_author when "snippet", "project_snippet" project.snippets.find(target_id).notes else diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index ed402b698fb..66a720a9426 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -130,7 +130,7 @@ module CommitsHelper def revert_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true) return unless current_user - tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip + tooltip = "Revert this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip if can_collaborate_with_project? btn_class = "btn btn-warning btn-#{btn_class}" unless btn_class.nil? @@ -154,7 +154,7 @@ module CommitsHelper def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true) return unless current_user - tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request" + tooltip = "Cherry-pick this #{commit.change_type_title(current_user)} in a new merge request" if can_collaborate_with_project? btn_class = "btn btn-default btn-#{btn_class}" unless btn_class.nil? diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index f489f9aa0d6..c35d6611ab0 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -55,7 +55,9 @@ module DiffHelper if line.blank? " ".html_safe else - line.sub(/^[\-+ ]/, '').html_safe + # We can't use `sub` because the HTML-safeness of `line` will not survive. + line[0] = '' if line.start_with?('+', '-', ' ') + line end end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index f1a0b929d82..362046c0270 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -45,6 +45,12 @@ module EventsHelper @project.feature_available?(feature_key, current_user) end + def comments_visible? + event_filter_visible(:repository) || + event_filter_visible(:merge_requests) || + event_filter_visible(:issues) + end + def event_preposition(event) if event.push? || event.commented? || event.target "at" diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 19ab059aea6..f6d4ea4659a 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -5,7 +5,7 @@ module GroupsHelper def group_icon(group) if group.is_a?(String) - group = Group.find_by(path: group) + group = Group.find_by_full_path(group) end group.try(:avatar_url) || image_path('no_group_avatar.png') diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index caf6908505e..fda8228a1e9 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -21,8 +21,6 @@ module Ci after_create :keep_around_commits, unless: :importing? - delegate :stages, to: :statuses - state_machine :status, initial: :created do event :enqueue do transition created: :pending @@ -98,17 +96,35 @@ module Ci sha[0...8] end - def self.stages - # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries - CommitStatus.where(pipeline: pluck(:id)).stages - end - def self.total_duration where.not(duration: nil).sum(:duration) end - def stages_with_latest_statuses - statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage) + def stages_count + statuses.select(:stage).distinct.count + end + + def stages_name + statuses.order(:stage_idx).distinct. + pluck(:stage, :stage_idx).map(&:first) + end + + def stages + status_sql = statuses.latest.where('stage=sg.stage').status_sql + + stages_query = statuses.group('stage').select(:stage) + .order('max(stage_idx)') + + stages_with_statuses = CommitStatus.from(stages_query, :sg). + pluck('sg.stage', status_sql) + + stages_with_statuses.map do |stage| + Ci::Stage.new(self, name: stage.first, status: stage.last) + end + end + + def artifacts + builds.latest.with_artifacts_not_expired end def project_id diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb new file mode 100644 index 00000000000..d2a37c0a827 --- /dev/null +++ b/app/models/ci/stage.rb @@ -0,0 +1,37 @@ +module Ci + # Currently this is artificial object, constructed dynamically + # We should migrate this object to actual database record in the future + class Stage + include StaticModel + + attr_reader :pipeline, :name + + delegate :project, to: :pipeline + + def initialize(pipeline, name:, status: nil) + @pipeline = pipeline + @name = name + @status = status + end + + def to_param + name + end + + def status + @status ||= statuses.latest.status + end + + def detailed_status + Gitlab::Ci::Status::Stage::Factory.new(self).fabricate! + end + + def statuses + @statuses ||= pipeline.statuses.where(stage: name) + end + + def builds + @builds ||= pipeline.builds.where(stage: name) + end + end +end diff --git a/app/models/commit.rb b/app/models/commit.rb index 248140f421b..1831cc7e175 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -245,44 +245,47 @@ class Commit project.repository.next_branch("cherry-pick-#{short_id}", mild: true) end - def revert_description - if merged_merge_request - "This reverts merge request #{merged_merge_request.to_reference}" + def revert_description(user) + if merged_merge_request?(user) + "This reverts merge request #{merged_merge_request(user).to_reference}" else "This reverts commit #{sha}" end end - def revert_message - %Q{Revert "#{title.strip}"\n\n#{revert_description}} + def revert_message(user) + %Q{Revert "#{title.strip}"\n\n#{revert_description(user)}} end - def reverts_commit?(commit) - description? && description.include?(commit.revert_description) + def reverts_commit?(commit, user) + description? && description.include?(commit.revert_description(user)) end def merge_commit? parents.size > 1 end - def merged_merge_request - return @merged_merge_request if defined?(@merged_merge_request) - - @merged_merge_request = project.merge_requests.find_by(merge_commit_sha: id) if merge_commit? + def merged_merge_request(current_user) + # Memoize with per-user access check + @merged_merge_request_hash ||= Hash.new do |hash, user| + hash[user] = merged_merge_request_no_cache(user) + end + + @merged_merge_request_hash[current_user] end - def has_been_reverted?(current_user = nil, noteable = self) + def has_been_reverted?(current_user, noteable = self) ext = all_references(current_user) noteable.notes_with_associations.system.each do |note| note.all_references(current_user, extractor: ext) end - ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self) } + ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self, current_user) } end - def change_type_title - merged_merge_request ? 'merge request' : 'commit' + def change_type_title(user) + merged_merge_request?(user) ? 'merge request' : 'commit' end # Get the URI type of the given path @@ -350,4 +353,12 @@ class Commit changes end + + def merged_merge_request?(user) + !!merged_merge_request(user) + end + + def merged_merge_request_no_cache(user) + MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit? + end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index c345bf293c9..cf90475f4d4 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -31,18 +31,13 @@ class CommitStatus < ActiveRecord::Base end scope :exclude_ignored, -> do - quoted_when = connection.quote_column_name('when') # We want to ignore failed_but_allowed jobs where("allow_failure = ? OR status IN (?)", - false, all_state_names - [:failed, :canceled]). - # We want to ignore skipped manual jobs - where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). - # We want to ignore skipped on_failure - where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped') + false, all_state_names - [:failed, :canceled]) end - scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) } - scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) } + scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) } + scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) } state_machine :status do event :enqueue do @@ -117,20 +112,6 @@ class CommitStatus < ActiveRecord::Base name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip end - def self.stages - # We group by stage name, but order stages by theirs' index - unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') - end - - def self.stages_status - # We execute subquery for each stage to calculate a stage status - statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql) - statuses.inject({}) do |h, k| - h[k.first] = k.last - h - end - end - def failed_but_allowed? allow_failure? && (failed? || canceled?) end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 2f5aa91a964..90432fc4050 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -4,7 +4,7 @@ module HasStatus AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped] STARTED_STATUSES = %w[running success failed skipped] ACTIVE_STATUSES = %w[pending running] - COMPLETED_STATUSES = %w[success failed canceled] + COMPLETED_STATUSES = %w[success failed canceled skipped] ORDERED_STATUSES = %w[failed pending running canceled success skipped] class_methods do @@ -23,9 +23,10 @@ module HasStatus canceled = scope.canceled.select('count(*)').to_sql "(CASE + WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success}) THEN 'success' WHEN (#{builds})=(#{created}) THEN 'created' - WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'skipped' + WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running' diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index e65fc9eaa09..875e9834487 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -1,17 +1,17 @@ module Milestoneish - def closed_items_count(user = nil) + def closed_items_count(user) issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size end - def total_items_count(user = nil) + def total_items_count(user) issues_visible_to_user(user).size + merge_requests.size end - def complete?(user = nil) + def complete?(user) total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user) end - def percent_complete(user = nil) + def percent_complete(user) ((closed_items_count(user) * 100) / total_items_count(user)).abs rescue ZeroDivisionError 0 @@ -29,7 +29,7 @@ module Milestoneish (Date.today - start_date).to_i end - def issues_visible_to_user(user = nil) + def issues_visible_to_user(user) issues.visible_to_user(user) end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb new file mode 100644 index 00000000000..d36bb9da296 --- /dev/null +++ b/app/models/concerns/routable.rb @@ -0,0 +1,70 @@ +# Store object full path in separate table for easy lookup and uniq validation +# Object must have path db field and respond to full_path and full_path_changed? methods. +module Routable + extend ActiveSupport::Concern + + included do + has_one :route, as: :source, autosave: true, dependent: :destroy + + validates_associated :route + + before_validation :update_route_path, if: :full_path_changed? + end + + class_methods do + # Finds a single object by full path match in routes table. + # + # Usage: + # + # Klass.find_by_full_path('gitlab-org/gitlab-ce') + # + # Returns a single object, or nil. + def find_by_full_path(path) + # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so + # any literal matches come first, for this we have to use "BINARY". + # Without this there's still no guarantee in what order MySQL will return + # rows. + binary = Gitlab::Database.mysql? ? 'BINARY' : '' + + order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)" + + where_paths_in([path]).reorder(order_sql).take + end + + # Builds a relation to find multiple objects by their full paths. + # + # Usage: + # + # Klass.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee}) + # + # Returns an ActiveRecord::Relation. + def where_paths_in(paths) + wheres = [] + cast_lower = Gitlab::Database.postgresql? + + paths.each do |path| + path = connection.quote(path) + where = "(routes.path = #{path})" + + if cast_lower + where = "(#{where} OR (LOWER(routes.path) = LOWER(#{path})))" + end + + wheres << where + end + + if wheres.empty? + none + else + joins(:route).where(wheres.join(' OR ')) + end + end + end + + private + + def update_route_path + route || build_route(source: self) + route.path = full_path + end +end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 33b578e12c1..ea3cf1cdaac 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -101,7 +101,9 @@ class MergeRequest < ActiveRecord::Base validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?] validate :validate_fork, unless: :closed_without_fork? - scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } + scope :by_source_or_target_branch, ->(branch_name) do + where("source_branch = :branch OR target_branch = :branch", branch: branch_name) + end scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :of_projects, ->(ids) { where(target_project_id: ids) } @@ -805,7 +807,7 @@ class MergeRequest < ActiveRecord::Base @merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha end - def can_be_reverted?(current_user = nil) + def can_be_reverted?(current_user) merge_commit && !merge_commit.has_been_reverted?(current_user, self) end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 7a545f752b6..37374044551 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -4,12 +4,16 @@ class Namespace < ActiveRecord::Base include CacheMarkdownField include Sortable include Gitlab::ShellAdapter + include Routable cache_markdown_field :description, pipeline: :description has_many :projects, dependent: :destroy belongs_to :owner, class_name: "User" + belongs_to :parent, class_name: "Namespace" + has_many :children, class_name: "Namespace", foreign_key: :parent_id + validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :name, presence: true, @@ -86,7 +90,7 @@ class Namespace < ActiveRecord::Base end def to_param - path + full_path end def human_name @@ -150,6 +154,14 @@ class Namespace < ActiveRecord::Base Gitlab.config.lfs.enabled end + def full_path + if parent + parent.full_path + '/' + path + else + path + end + end + private def repository_storage_paths @@ -185,4 +197,8 @@ class Namespace < ActiveRecord::Base where(projects: { namespace_id: id }). find_each(&:refresh_members_authorized_projects) end + + def full_path_changed? + path_changed? || parent_id_changed? + end end diff --git a/app/models/project.rb b/app/models/project.rb index 590885c0177..77d740081c6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -14,6 +14,7 @@ class Project < ActiveRecord::Base include TokenAuthenticatable include ProjectFeaturesCompatibility include SelectForProjectAuthorization + include Routable extend Gitlab::ConfigHelper @@ -324,87 +325,6 @@ class Project < ActiveRecord::Base non_archived.where(table[:name].matches(pattern)) end - # Finds a single project for the given path. - # - # path - The full project path (including namespace path). - # - # Returns a Project, or nil if no project could be found. - def find_with_namespace(path) - namespace_path, project_path = path.split('/', 2) - - return unless namespace_path && project_path - - namespace_path = connection.quote(namespace_path) - project_path = connection.quote(project_path) - - # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so - # any literal matches come first, for this we have to use "BINARY". - # Without this there's still no guarantee in what order MySQL will return - # rows. - binary = Gitlab::Database.mysql? ? 'BINARY' : '' - - order_sql = "(CASE WHEN #{binary} namespaces.path = #{namespace_path} " \ - "AND #{binary} projects.path = #{project_path} THEN 0 ELSE 1 END)" - - where_paths_in([path]).reorder(order_sql).take - end - - # Builds a relation to find multiple projects by their full paths. - # - # Each path must be in the following format: - # - # namespace_path/project_path - # - # For example: - # - # gitlab-org/gitlab-ce - # - # Usage: - # - # Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee}) - # - # This would return the projects with the full paths matching the values - # given. - # - # paths - An Array of full paths (namespace path + project path) for which - # to find the projects. - # - # Returns an ActiveRecord::Relation. - def where_paths_in(paths) - wheres = [] - cast_lower = Gitlab::Database.postgresql? - - paths.each do |path| - namespace_path, project_path = path.split('/', 2) - - next unless namespace_path && project_path - - namespace_path = connection.quote(namespace_path) - project_path = connection.quote(project_path) - - where = "(namespaces.path = #{namespace_path} - AND projects.path = #{project_path})" - - if cast_lower - where = "( - #{where} - OR ( - LOWER(namespaces.path) = LOWER(#{namespace_path}) - AND LOWER(projects.path) = LOWER(#{project_path}) - ) - )" - end - - wheres << where - end - - if wheres.empty? - none - else - joins(:namespace).where(wheres.join(' OR ')) - end - end - def visibility_levels Gitlab::VisibilityLevel.options end @@ -440,6 +360,10 @@ class Project < ActiveRecord::Base def group_ids joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id) end + + # Add alias for Routable method for compatibility with old code. + # In future all calls `find_with_namespace` should be replaced with `find_by_full_path` + alias_method :find_with_namespace, :find_by_full_path end def lfs_enabled? @@ -879,13 +803,14 @@ class Project < ActiveRecord::Base end alias_method :human_name, :name_with_namespace - def path_with_namespace - if namespace - namespace.path + '/' + path + def full_path + if namespace && path + namespace.full_path + '/' + path else path end end + alias_method :path_with_namespace, :full_path def execute_hooks(data, hooks_scope = :push_hooks) hooks.send(hooks_scope).each do |hook| @@ -1373,4 +1298,8 @@ class Project < ActiveRecord::Base def validate_board_limit(board) raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS end + + def full_path_changed? + path_changed? || namespace_id_changed? + end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 4034a49ae63..44f66b89600 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -940,7 +940,7 @@ class Repository committer = user_to_committer(user) Rugged::Commit.create(rugged, - message: commit.revert_message, + message: commit.revert_message(user), author: committer, committer: committer, tree: revert_tree_id, diff --git a/app/models/route.rb b/app/models/route.rb new file mode 100644 index 00000000000..d40214b9da6 --- /dev/null +++ b/app/models/route.rb @@ -0,0 +1,22 @@ +class Route < ActiveRecord::Base + belongs_to :source, polymorphic: true + + validates :source, presence: true + + validates :path, + length: { within: 1..255 }, + presence: true, + uniqueness: { case_sensitive: false } + + after_update :rename_children, if: :path_changed? + + def rename_children + # We update each row separately because MySQL does not have regexp_replace. + # rubocop:disable Rails/FindEach + Route.where('path LIKE ?', "#{path_was}%").each do |route| + # Note that update column skips validation and callbacks. + # We need this to avoid recursive call of rename_children method + route.update_column(:path, route.path.sub(path_was, path)) + end + end +end diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 2e028c44d8b..79eb97b7b55 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -44,11 +44,11 @@ module Ci def valid_statuses_for_when(value) case value when 'on_success' - %w[success] + %w[success skipped] when 'on_failure' %w[failed] when 'always' - %w[success failed] + %w[success failed skipped] else [] end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index d49fcd42a08..99d459908e7 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -41,7 +41,7 @@ module Commits success else - error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically. + error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically. It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content." raise ChangeError, error_msg end diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb index a880952e274..2316c57bf1e 100644 --- a/app/services/destroy_group_service.rb +++ b/app/services/destroy_group_service.rb @@ -20,6 +20,10 @@ class DestroyGroupService ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute end + group.children.each do |group| + DestroyGroupService.new(group, current_user).async_execute + end + group.really_destroy! end end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index e4056306bc4..0a9563ed7e7 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -55,8 +55,9 @@ module MergeRequests # Refresh merge request diff if we push to source or target branch of merge request # Note: we should update merge requests from forks too def reload_merge_requests - merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a - merge_requests += fork_merge_requests.by_branch(@branch_name).to_a + merge_requests = @project.merge_requests.opened. + by_source_or_target_branch(@branch_name).to_a + merge_requests += fork_merge_requests merge_requests = filter_merge_requests(merge_requests) merge_requests.each do |merge_request| @@ -157,13 +158,14 @@ module MergeRequests def merge_requests_for_source_branch @source_merge_requests ||= begin merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a - merge_requests += fork_merge_requests.where(source_branch: @branch_name).to_a + merge_requests += fork_merge_requests filter_merge_requests(merge_requests) end end def fork_merge_requests - @fork_merge_requests ||= @project.fork_merge_requests.opened + @fork_merge_requests ||= @project.fork_merge_requests.opened. + where(source_branch: @branch_name).to_a end def branch_added? diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb index b10ad71d052..fbaea2744a3 100644 --- a/app/uploaders/uploader_helper.rb +++ b/app/uploaders/uploader_helper.rb @@ -1,6 +1,6 @@ # Extra methods for uploader module UploaderHelper - IMAGE_EXT = %w[png jpg jpeg gif bmp tiff] + IMAGE_EXT = %w[png jpg jpeg gif bmp tiff svg] # We recommend using the .mp4 format over .mov. Videos in .mov format can # still be used but you really need to make sure they are served with the # proper MIME type video/mp4 and not video/quicktime or your videos won't play diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index fa8e7979461..af87129e49e 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,4 +1,5 @@ - page_title "Sign in" + %div - if form_based_providers.any? = render 'devise/shared/tabs_ldap' diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 701bcd3ab71..7bd11f5727a 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -77,7 +77,7 @@ = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do %span Merge Requests - %span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) + %span.badge.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count) - if project_nav_tab? :wiki = nav_link(controller: :wikis) do diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 697c8d19257..56c1949ab2b 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -133,7 +133,7 @@ %tr.success-message %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"} - build_count = @pipeline.statuses.latest.size - - stage_count = @pipeline.stages.size + - stage_count = @pipeline.stages_count Pipeline %a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"} = "\##{@pipeline.id}" diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index ae22d474f2c..40e5e306426 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -16,7 +16,7 @@ Commit Author: <%= commit.author_name %> <% end -%> <% build_count = @pipeline.statuses.latest.size -%> -<% stage_count = @pipeline.stages.size -%> +<% stage_count = @pipeline.stages_count -%> Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>. diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml new file mode 100644 index 00000000000..afe2fd7fd7b --- /dev/null +++ b/app/views/projects/_merge_request_merge_settings.html.haml @@ -0,0 +1,15 @@ +- form = local_assigns.fetch(:form) + +.form-group + .checkbox.builds-feature + = form.label :only_allow_merge_if_build_succeeds do + = form.check_box :only_allow_merge_if_build_succeeds + %strong Only allow merge requests to be merged if the build succeeds + %br + %span.descr + Builds need to be configured to enable this feature. + = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') + .checkbox + = form.label :only_allow_merge_if_all_discussions_are_resolved do + = form.check_box :only_allow_merge_if_all_discussions_are_resolved + %strong Only allow merge requests to be merged if all discussions are resolved diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index 6e143c4b570..818010bc7d3 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -1,18 +1,8 @@ -.merge-requests-feature - %fieldset.builds-feature - %hr - %h5.prepend-top-0 - Merge Requests - .form-group - .checkbox - = f.label :only_allow_merge_if_build_succeeds do - = f.check_box :only_allow_merge_if_build_succeeds - %strong Only allow merge requests to be merged if the build succeeds - %br - %span.descr - Builds need to be configured to enable this feature. - = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') - .checkbox - = f.label :only_allow_merge_if_all_discussions_are_resolved do - = f.check_box :only_allow_merge_if_all_discussions_are_resolved - %strong Only allow merge requests to be merged if all discussions are resolved +- form = local_assigns.fetch(:form) + +%fieldset.features.merge-requests-feature.append-bottom-default + %hr + %h5.prepend-top-0 + Merge Requests + + = render 'projects/merge_request_merge_settings', form: form diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index d5004f6a066..ce8b66b1945 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -111,7 +111,7 @@ %span.label.label-primary = tag - - if @build.pipeline.stages.many? + - if @build.pipeline.stages_count > 1 .dropdown.build-dropdown .title Stage %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} @@ -120,7 +120,7 @@ %ul.dropdown-menu - @build.pipeline.stages.each do |stage| %li - %a.stage-item= stage + %a.stage-item= stage.name .builds-container - HasStatus::ORDERED_STATUSES.each do |build_status| diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index e75547c815f..18b3b04154f 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -104,9 +104,9 @@ = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - elsif allow_retry - - if build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do - = icon('repeat') - - elsif build.playable? && !admin + - if build.playable? && !admin = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = custom_icon('icon_play') + - elsif build.retryable? + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + = icon('repeat') diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 0f08f4e8592..b58dceb58c9 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -43,15 +43,13 @@ - else Cant find HEAD commit for this branch - - stages_status = pipeline.statuses.latest.stages_status %td.stage-cell - - stages.each do |stage| - - status = stages_status[stage] - - tooltip = "#{stage.titleize}: #{status || 'not found'}" - - if status + - pipeline.stages.each do |stage| + - if stage.status + - tooltip = "#{stage.name.titleize}: #{stage.status || 'not found'}" .stage-container - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do - = ci_icon_for_status(status) + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage.name), class: "has-tooltip ci-status-icon-#{stage.status}", title: tooltip do + = ci_icon_for_status(stage.status) %td - if pipeline.duration diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index e4cd55b9f7a..f6e3d5e76f5 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -11,7 +11,7 @@ .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3.page-title== #{label} this #{commit.change_type_title} + %h3.page-title== #{label} this #{commit.change_type_title(current_user)} .modal-body = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do .form-group.branch diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml deleted file mode 100644 index 3a3d750439f..00000000000 --- a/app/views/projects/commit/_ci_stage.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -%tr - %th{colspan: 10} - %strong - %a{name: stage} - - status = statuses.latest.status - %span{class: "ci-status-link ci-status-icon-#{status}"} - = ci_icon_for_status(status) - - if stage - - = stage.titleize - = render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true - = render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true -%tr - %td{colspan: 10} - diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 1174158eb65..c7b5c1124b3 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -24,20 +24,8 @@ in = time_interval_in_words pipeline.duration - .row-content-block.build-content.middle-block.pipeline-graph.hidden - .pipeline-visualization - %ul.stage-column-list - - stages = pipeline.stages_with_latest_statuses - - stages.each do |stage, statuses| - %li.stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - %ul - = render "projects/commit/pipeline_stage", statuses: statuses - + .row-content-block.build-content.middle-block.hidden + = render "projects/pipelines/graph", pipeline: pipeline - if pipeline.yaml_errors.present? .bs-callout.bs-callout-danger @@ -62,5 +50,4 @@ - if pipeline.project.build_coverage_enabled? %th Coverage %th - - pipeline.statuses.relevant.stages.each do |stage| - = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage) + = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml deleted file mode 100644 index f9a9c8707f5..00000000000 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -- status_groups = statuses.sort_by(&:name).group_by(&:group_name) -- status_groups.each do |group_name, grouped_statuses| - - if grouped_statuses.one? - - status = grouped_statuses.first - - is_playable = status.playable? && can?(current_user, :update_build, @project) - %li.build{ class: ("playable" if is_playable) } - .curve - .build-content - = render "projects/#{status.to_partial_path}_pipeline", subject: status - - else - %li.build - .curve - .dropdown.inline.build-content - = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 2dc91a9b762..7f42fde0fea 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -12,4 +12,4 @@ %th Stages %th %th - = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, show_commit: false + = render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 3a5af2723c6..01cd8fa0938 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -112,7 +112,8 @@ %span.descr Enable Container Registry for this project = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank' - = render 'merge_request_settings', f: f + = render 'merge_request_settings', form: f + %hr %fieldset.features.append-bottom-default %h5.prepend-top-0 diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index a9235d6af35..a65a630f2d0 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -17,4 +17,6 @@ "project-stopped-environments-path" => project_environments_path(@project, scope: :stopped), "new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project), "help-page-path" => help_page_path("ci/environments"), - "css-class" => container_class}} + "css-class" => container_class, + "commit-icon-svg" => custom_icon("icon_commit"), + "play-icon-svg" => custom_icon("icon_play")}} diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml index 3d0ab5b85d6..98d81308407 100644 --- a/app/views/projects/forks/error.html.haml +++ b/app/views/projects/forks/error.html.haml @@ -13,7 +13,11 @@ - if @forked_project && @forked_project.errors.any? %p – - = @forked_project.errors.full_messages.first + - error = @forked_project.errors.full_messages.first + - if error.include?("already been taken") + Name has already been taken + - else + = error %p = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 0e2975bd551..896f10104fa 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -31,7 +31,7 @@ %span.label-branch= source_branch_with_namespace(@merge_request) %span into %span.label-branch - = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) + = link_to_if @merge_request.target_branch_exists?, @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) - if @merge_request.open? && @merge_request.diverged_from_target_branch? %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) diff --git a/app/views/projects/pipelines/_graph.html.haml b/app/views/projects/pipelines/_graph.html.haml new file mode 100644 index 00000000000..0202833c0bf --- /dev/null +++ b/app/views/projects/pipelines/_graph.html.haml @@ -0,0 +1,4 @@ +- pipeline = local_assigns.fetch(:pipeline) +.pipeline-visualization.pipeline-graph + %ul.stage-column-list + = render partial: "projects/stage/graph", collection: pipeline.stages, as: :stage diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index 3464e155a1b..739e5930822 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -12,19 +12,8 @@ .tab-content #js-tab-pipeline.tab-pane - .build-content.middle-block.pipeline-graph - .pipeline-visualization - %ul.stage-column-list - - stages = pipeline.stages_with_latest_statuses - - stages.each do |stage, statuses| - %li.stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - %ul - = render "projects/commit/pipeline_stage", statuses: statuses + .build-content.middle-block + = render "projects/pipelines/graph", pipeline: pipeline #js-tab-builds.tab-pane - if pipeline.yaml_errors.present? @@ -50,5 +39,4 @@ - if pipeline.project.build_coverage_enabled? %th Coverage %th - - pipeline.statuses.relevant.stages.each do |stage| - = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage) + = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4bc49072f35..e1e787dbde4 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -37,7 +37,6 @@ %span CI Lint %div.content-list.pipelines - - stages = @pipelines.stages - if @pipelines.blank? %div .nothing-here-block No pipelines to show @@ -51,6 +50,6 @@ %th Stages %th %th.hidden-xs - = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages + = render @pipelines, commit_sha: true, stage: true, allow_retry: true = paginate @pipelines, theme: 'gitlab' diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml new file mode 100644 index 00000000000..1d8fa10db0c --- /dev/null +++ b/app/views/projects/stage/_graph.html.haml @@ -0,0 +1,22 @@ +- stage = local_assigns.fetch(:stage) +- statuses = stage.statuses.latest +- status_groups = statuses.sort_by(&:name).group_by(&:group_name) +%li.stage-column + .stage-name + %a{ name: stage.name } + = stage.name.titleize + .builds-container + %ul + - status_groups.each do |group_name, grouped_statuses| + - if grouped_statuses.one? + - status = grouped_statuses.first + - is_playable = status.playable? && can?(current_user, :update_build, @project) + %li.build{ class: ("playable" if is_playable) } + .curve + .build-content + = render "projects/#{status.to_partial_path}_pipeline", subject: status + - else + %li.build + .curve + .dropdown.inline.build-content + = render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/stage/_in_stage_group.html.haml index 2b26ad9d6fa..2b26ad9d6fa 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/stage/_in_stage_group.html.haml diff --git a/app/views/projects/stage/_stage.html.haml b/app/views/projects/stage/_stage.html.haml new file mode 100644 index 00000000000..1684e02fbad --- /dev/null +++ b/app/views/projects/stage/_stage.html.haml @@ -0,0 +1,13 @@ +%tr + %th{colspan: 10} + %strong + %a{ name: stage.name } + %span{class: "ci-status-link ci-status-icon-#{stage.status}"} + = ci_icon_for_status(stage.status) + + = stage.name.titleize += render stage.statuses.latest_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true += render stage.statuses.retried_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true +%tr + %td{colspan: 10} + diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index c367ae336db..e50ab5fea09 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -5,5 +5,7 @@ - if event_filter_visible(:merge_requests) = event_filter_link EventFilter.merged, 'Merge events' - if event_filter_visible(:issues) + = event_filter_link EventFilter.issue, 'Issue events' + - if comments_visible? = event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.team, 'Team' diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 432047a1c4e..e67f7d5a352 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -20,8 +20,8 @@ %strong Blocked - if source.instance_of?(Group) && !@group - = link_to source, class: "member-group-link prepend-left-5" do - = "· #{source.name}" + · + = link_to source.name, source, class: "member-group-link" .hidden-xs.cgray - if member.request? @@ -45,7 +45,7 @@ = time_ago_with_tooltip(member.created_at) - if show_roles .controls.member-controls - - if show_controls + - if show_controls && (member.respond_to?(:group) && @group) || (member.respond_to?(:project) && @project) - if user != current_user = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml index 0a237136959..d27fba805a3 100644 --- a/app/views/shared/milestones/_summary.html.haml +++ b/app/views/shared/milestones/_summary.html.haml @@ -25,8 +25,10 @@ %span.milestone-stat %strong== #{milestone.percent_complete(current_user)}% complete - %span.milestone-stat - %span.remaining-days= milestone_remaining_days(milestone) + - remaining_days = milestone_remaining_days(milestone) + - if remaining_days.present? + %span.milestone-stat + %span.remaining-days= remaining_days .milestone-progress-buttons %span.tab-issues-buttons |