diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2018-05-18 17:25:24 +0100 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2018-05-18 17:25:24 +0100 |
commit | 7f0f8594d3e5cbee41771c476e3b7496946db300 (patch) | |
tree | 1981c15675409386908b3b05d4f1aa75b1755957 /app | |
parent | 068186555cb85e85bbfe04afe858fb3eb4801207 (diff) | |
parent | 8bacfbd1cc4f6c3678d50dd516df2b59eb0c8864 (diff) | |
download | gitlab-ce-7f0f8594d3e5cbee41771c476e3b7496946db300.tar.gz |
Merge branch 'master' into 5845-extract-ee-environments-files5845-extract-ee-environments-files
* master: (69 commits)
Calculating repository checksums executed by Gitaly
Resolve "Expand API: Render an arbitrary Markdown document"
Update EE > CE downgrade service removal steps
Make stores export a createStore() which can be used in tests
Simplify pattern lexeme fabrication and matcher
Simplify untrusted regexp factory method
Fix api_json.log not always reporting the right HTTP status code
Move group lists css from framework/lists.scss to pages/groups.scss
Resolve "Web IDE: Previewing Markdown in Firefox doesn’t show a scroll bar"
Add Keyboard shortcuts for "Kubernetes" and "Environments"
Move API group deletion to Sidekiq
fix typos. add a reference to deliverable and stretch for design artifact
fix / assigne username wrapping problem has been fixed
Memoize Gitlab::Database.version
Conditionally add Gitaly deprecation warnings based on ENV variable
Bring CE-EE parity to app/services/milestones/base_service.rb
Bring CE-EE parity to app/services/lfs/unlock_file_service.rb
Fixes 500 error on /estimate BIG_VALUE
Fix: Use case in-sensitive ordering by name for groups
Fix group lists visual
...
Diffstat (limited to 'app')
45 files changed, 611 insertions, 456 deletions
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue index 4a645c827ad..81961fe3c57 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue @@ -5,7 +5,7 @@ import LoadingButton from '~/vue_shared/components/loading_button.vue'; import CommitMessageField from './message_field.vue'; import Actions from './actions.vue'; import SuccessMessage from './success_message.vue'; -import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT, COMMIT_ITEM_PADDING } from '../../constants'; +import { activityBarViews, MAX_WINDOW_HEIGHT_COMPACT } from '../../constants'; export default { components: { @@ -70,7 +70,7 @@ export default { ? this.$refs.formEl && this.$refs.formEl.offsetHeight : this.$refs.compactEl && this.$refs.compactEl.offsetHeight; - this.componentHeight = elHeight + COMMIT_ITEM_PADDING; + this.componentHeight = elHeight; }, enterTransition() { this.$nextTick(() => { @@ -78,7 +78,7 @@ export default { ? this.$refs.compactEl && this.$refs.compactEl.offsetHeight : this.$refs.formEl && this.$refs.formEl.offsetHeight; - this.componentHeight = elHeight + COMMIT_ITEM_PADDING; + this.componentHeight = elHeight; }); }, afterEndTransition() { diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index 14946f8c9fa..7bc865058c6 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -122,11 +122,11 @@ export default { <div class="file" :class="fileClass" + @click="clickFile" + role="button" > <div class="file-name" - @click="clickFile" - role="button" > <span class="ide-file-name str-truncated" diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js index 48d4cc43198..83fe22f40a4 100644 --- a/app/assets/javascripts/ide/constants.js +++ b/app/assets/javascripts/ide/constants.js @@ -5,8 +5,6 @@ export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33; export const MAX_WINDOW_HEIGHT_COMPACT = 750; -export const COMMIT_ITEM_PADDING = 32; - // Commit message textarea export const MAX_TITLE_LENGTH = 50; export const MAX_BODY_LENGTH = 72; diff --git a/app/assets/javascripts/pipelines/components/graph/action_component.vue b/app/assets/javascripts/pipelines/components/graph/action_component.vue index fd3491c7fe0..82b4ce083fb 100644 --- a/app/assets/javascripts/pipelines/components/graph/action_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/action_component.vue @@ -1,11 +1,21 @@ <script> import $ from 'jquery'; -import tooltip from '../../../vue_shared/directives/tooltip'; -import Icon from '../../../vue_shared/components/icon.vue'; -import { dasherize } from '../../../lib/utils/text_utility'; -import eventHub from '../../event_hub'; +import axios from '~/lib/utils/axios_utils'; +import { dasherize } from '~/lib/utils/text_utility'; +import { __ } from '~/locale'; +import createFlash from '~/flash'; +import tooltip from '~/vue_shared/directives/tooltip'; +import Icon from '~/vue_shared/components/icon.vue'; + /** - * Renders either a cancel, retry or play icon pointing to the given path. + * Renders either a cancel, retry or play icon button and handles the post request + * + * Used in: + * - mr widget mini pipeline graph: `mr_widget_pipeline.vue` + * - pipelines table + * - pipelines table in merge request page + * - pipelines table in commit page + * - pipelines detail page in big graph */ export default { components: { @@ -32,16 +42,10 @@ export default { required: true, }, - requestFinishedFor: { - type: String, - required: false, - default: '', - }, }, data() { return { isDisabled: false, - linkRequested: '', }; }, @@ -51,19 +55,28 @@ export default { return `${actionIconDash} js-icon-${actionIconDash}`; }, }, - watch: { - requestFinishedFor() { - if (this.requestFinishedFor === this.linkRequested) { - this.isDisabled = false; - } - }, - }, methods: { + /** + * The request should not be handled here. + * However due to this component being used in several + * different apps it avoids repetition & complexity. + * + */ onClickAction() { $(this.$el).tooltip('hide'); - eventHub.$emit('postAction', this.link); - this.linkRequested = this.link; + this.isDisabled = true; + + axios.post(`${this.link}.json`) + .then(() => { + this.isDisabled = false; + this.$emit('pipelineActionRequestComplete'); + }) + .catch(() => { + this.isDisabled = false; + + createFlash(__('An error occurred while making the request.')); + }); }, }, }; @@ -80,6 +93,6 @@ btn-transparent ci-action-icon-container ci-action-icon-wrapper" data-container="body" :disabled="isDisabled" > - <icon :name="actionIcon" /> + <icon :name="actionIcon"/> </button> </template> diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue index 4027d26098f..7bfe11ab8cd 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue @@ -42,11 +42,6 @@ export default { type: Object, required: true, }, - requestFinishedFor: { - type: String, - required: false, - default: '', - }, }, computed: { @@ -76,11 +71,15 @@ export default { e.stopPropagation(); }); }, + + pipelineActionRequestComplete() { + this.$emit('pipelineActionRequestComplete'); + }, }, }; </script> <template> - <div class="ci-job-dropdown-container"> + <div class="ci-job-dropdown-container dropdown"> <button v-tooltip type="button" @@ -110,7 +109,7 @@ export default { <job-component :job="item" css-class-job-name="mini-pipeline-graph-dropdown-item" - :request-finished-for="requestFinishedFor" + @pipelineActionRequestComplete="pipelineActionRequestComplete" /> </li> </ul> diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 7b8a5edcbff..4ec67f6c01b 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -16,11 +16,6 @@ export default { type: Object, required: true, }, - requestFinishedFor: { - type: String, - required: false, - default: '', - }, }, computed: { @@ -51,6 +46,10 @@ export default { return className; }, + + refreshPipelineGraph() { + this.$emit('refreshPipelineGraph'); + }, }, }; </script> @@ -74,7 +73,7 @@ export default { :key="stage.name" :stage-connector-class="stageConnectorClass(index, stage)" :is-first-column="isFirstColumn(index)" - :request-finished-for="requestFinishedFor" + @refreshPipelineGraph="refreshPipelineGraph" /> </ul> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue index c1f0f051b63..27b938c4985 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue @@ -46,11 +46,6 @@ export default { required: false, default: '', }, - requestFinishedFor: { - type: String, - required: false, - default: '', - }, }, computed: { status() { @@ -84,6 +79,11 @@ export default { return this.job.status && this.job.status.action && this.job.status.action.path; }, }, + methods: { + pipelineActionRequestComplete() { + this.$emit('pipelineActionRequestComplete'); + }, + }, }; </script> <template> @@ -126,7 +126,7 @@ export default { :tooltip-text="status.action.title" :link="status.action.path" :action-icon="status.action.icon" - :request-finished-for="requestFinishedFor" + @pipelineActionRequestComplete="pipelineActionRequestComplete" /> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index 5461fdbbadd..f32368947e8 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -29,12 +29,6 @@ export default { required: false, default: '', }, - - requestFinishedFor: { - type: String, - required: false, - default: '', - }, }, methods: { @@ -49,6 +43,10 @@ export default { buildConnnectorClass(index) { return index === 0 && !this.isFirstColumn ? 'left-connector' : ''; }, + + pipelineActionRequestComplete() { + this.$emit('refreshPipelineGraph'); + }, }, }; </script> @@ -75,12 +73,13 @@ export default { v-if="job.size === 1" :job="job" css-class-job-name="build-content" + @pipelineActionRequestComplete="pipelineActionRequestComplete" /> <dropdown-job-component v-if="job.size > 1" :job="job" - :request-finished-for="requestFinishedFor" + @pipelineActionRequestComplete="pipelineActionRequestComplete" /> </li> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 498a97851fa..0f671ceea21 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -9,6 +9,7 @@ import CommitComponent from '../../vue_shared/components/commit.vue'; import LoadingButton from '../../vue_shared/components/loading_button.vue'; import Icon from '../../vue_shared/components/icon.vue'; + import { PIPELINES_TABLE } from '../constants'; /** * Pipeline table row. @@ -46,6 +47,7 @@ required: true, }, }, + pipelinesTable: PIPELINES_TABLE, data() { return { isRetrying: false, @@ -297,6 +299,7 @@ v-for="(stage, index) in pipeline.details.stages" :key="index"> <pipeline-stage + :type="$options.pipelinesTable" :stage="stage" :update-dropdown="updateGraphDropdown" /> diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index a65485c05eb..f9769815796 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -21,6 +21,7 @@ import Icon from '../../vue_shared/components/icon.vue'; import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import JobComponent from './graph/job_component.vue'; import tooltip from '../../vue_shared/directives/tooltip'; +import { PIPELINES_TABLE } from '../constants'; export default { components: { @@ -44,6 +45,12 @@ export default { required: false, default: false, }, + + type: { + type: String, + required: false, + default: '', + }, }, data() { @@ -133,6 +140,16 @@ export default { isDropdownOpen() { return this.$el.classList.contains('open'); }, + + pipelineActionRequestComplete() { + if (this.type === PIPELINES_TABLE) { + // warn the table to update + eventHub.$emit('refreshPipelinesTable'); + } else { + // close the dropdown in mr widget + $(this.$refs.dropdown).dropdown('toggle'); + } + }, }, }; </script> @@ -151,6 +168,7 @@ export default { id="stageDropdown" aria-haspopup="true" aria-expanded="false" + ref="dropdown" > <span @@ -188,6 +206,7 @@ export default { <job-component :job="job" css-class-job-name="mini-pipeline-graph-dropdown-item" + @pipelineActionRequestComplete="pipelineActionRequestComplete" /> </li> </ul> diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js index b384c7500e7..eaa11a84cb9 100644 --- a/app/assets/javascripts/pipelines/constants.js +++ b/app/assets/javascripts/pipelines/constants.js @@ -1,2 +1,2 @@ -// eslint-disable-next-line import/prefer-default-export export const CANCEL_REQUEST = 'CANCEL_REQUEST'; +export const PIPELINES_TABLE = 'PIPELINES_TABLE'; diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index de0faf181e5..30b1eee186d 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -55,11 +55,13 @@ export default { eventHub.$on('postAction', this.postAction); eventHub.$on('retryPipeline', this.postAction); eventHub.$on('clickedDropdown', this.updateTable); + eventHub.$on('refreshPipelinesTable', this.fetchPipelines); }, beforeDestroy() { eventHub.$off('postAction', this.postAction); eventHub.$off('retryPipeline', this.postAction); eventHub.$off('clickedDropdown', this.updateTable); + eventHub.$off('refreshPipelinesTable', this.fetchPipelines); }, destroyed() { this.poll.stop(); diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index 04fe7958fe6..b49a16a87e6 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -25,30 +25,14 @@ export default () => { data() { return { mediator, - requestFinishedFor: null, }; }, - created() { - eventHub.$on('postAction', this.postAction); - }, - beforeDestroy() { - eventHub.$off('postAction', this.postAction); - }, methods: { - postAction(action) { - // Click was made, reset this variable - this.requestFinishedFor = null; - - this.mediator.service - .postAction(action) - .then(() => { - this.mediator.refreshPipeline(); - this.requestFinishedFor = action; - }) - .catch(() => { - this.requestFinishedFor = action; - Flash(__('An error occurred while making the request.')); - }); + requestRefreshPipelineGraph() { + // When an action is clicked + // (wether in the dropdown or in the main nodes, we refresh the big graph) + this.mediator.refreshPipeline() + .catch(() => Flash(__('An error occurred while making the request.'))); }, }, render(createElement) { @@ -56,7 +40,9 @@ export default () => { props: { isLoading: this.mediator.state.isLoading, pipeline: this.mediator.store.state.pipeline, - requestFinishedFor: this.requestFinishedFor, + }, + on: { + refreshPipelineGraph: this.requestRefreshPipelineGraph, }, }); }, diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index a4d10850471..78f7353eb0d 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -7,7 +7,7 @@ export default class ShortcutsNavigation extends Shortcuts { super(); Mousetrap.bind('g p', () => findAndFollowLink('.shortcuts-project')); - Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-project-activity')); + Mousetrap.bind('g v', () => findAndFollowLink('.shortcuts-project-activity')); Mousetrap.bind('g f', () => findAndFollowLink('.shortcuts-tree')); Mousetrap.bind('g c', () => findAndFollowLink('.shortcuts-commits')); Mousetrap.bind('g j', () => findAndFollowLink('.shortcuts-builds')); @@ -16,9 +16,10 @@ export default class ShortcutsNavigation extends Shortcuts { Mousetrap.bind('g i', () => findAndFollowLink('.shortcuts-issues')); Mousetrap.bind('g b', () => findAndFollowLink('.shortcuts-issue-boards')); Mousetrap.bind('g m', () => findAndFollowLink('.shortcuts-merge_requests')); - Mousetrap.bind('g t', () => findAndFollowLink('.shortcuts-todos')); Mousetrap.bind('g w', () => findAndFollowLink('.shortcuts-wiki')); Mousetrap.bind('g s', () => findAndFollowLink('.shortcuts-snippets')); + Mousetrap.bind('g k', () => findAndFollowLink('.shortcuts-kubernetes')); + Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-environments')); Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue')); this.enabledHelp.push('.hidden-shortcut.project'); diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index f1a8a46dda4..45517416e93 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -279,251 +279,14 @@ ul.indent-list { padding: 10px 0 0 30px; } - // Specific styles for tree list @keyframes spin-avatar { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } -.groups-list-tree-container { - .has-no-search-results { - text-align: center; - padding: $gl-padding; - font-style: italic; - color: $well-light-text-color; - } - - > .group-list-tree > .group-row.has-children:first-child { - border-top: 0; - } -} - -.group-list-tree { - .avatar-container.content-loading { - position: relative; - - > a, - > a .avatar { - height: 100%; - border-radius: 50%; - } - - > a { - padding: 2px; - - .avatar { - border: 2px solid $white-normal; - - &.identicon { - line-height: 15px; - } - } - } - - &::after { - content: ""; - position: absolute; - height: 100%; - width: 100%; - background-color: transparent; - border: 2px outset $kdb-border; - border-radius: 50%; - animation: spin-avatar 3s infinite linear; - } - } - - .folder-toggle-wrap { - float: left; - line-height: $list-text-height; - font-size: 0; - - span { - font-size: $gl-font-size; - } - } - - .folder-caret, - .item-type-icon { - display: inline-block; - } - - .folder-caret { - width: 15px; - - svg { - margin-bottom: 2px; - } - } - - .item-type-icon { - margin-top: 2px; - width: 20px; - } - - > .group-row:not(.has-children) { - .folder-caret { - opacity: 0; - } - } - - .content-list li:last-child { - padding-bottom: 0; - } - - .group-list-tree { - margin-bottom: 0; - margin-left: 30px; - position: relative; - - &::before { - content: ''; - display: block; - width: 0; - position: absolute; - top: 5px; - bottom: 0; - left: -16px; - border-left: 2px solid $border-white-normal; - } - - .group-row { - position: relative; - - &::before { - content: ""; - display: block; - width: 10px; - height: 0; - border-top: 2px solid $border-white-normal; - position: absolute; - top: 30px; - left: -16px; - } - - &:last-child::before { - background: $white-light; - height: auto; - top: 30px; - bottom: 0; - } - - &.being-removed { - opacity: 0.5; - } - } - } - - .group-row { - padding: 0; - - &.has-children { - border-top: 0; - } - - &:first-child { - border-top: 1px solid $white-normal; - } - - &:last-of-type { - .group-row-contents:not(:hover) { - border-bottom: 1px solid transparent; - } - } - } - - .group-row-contents { - padding: 10px 10px 8px; - border-top: solid 1px transparent; - border-bottom: solid 1px $white-normal; - - &:hover { - border-color: $row-hover-border; - background-color: $row-hover; - cursor: pointer; - } - - .avatar-container > a { - width: 100%; - text-decoration: none; - } - - &.has-more-items { - display: block; - padding: 20px 10px; - } - - .stats { - position: relative; - line-height: 46px; - - > span { - display: inline-flex; - align-items: center; - height: 16px; - min-width: 30px; - } - - > span:last-child { - margin-right: 0; - } - - .stat-value { - margin: 2px 0 0 5px; - } - } - - .controls { - margin-left: 5px; - - > .btn { - margin-right: $btn-xs-side-margin; - } - } - } - - .project-row-contents .stats { - line-height: inherit; - - > span:first-child { - margin-left: 25px; - } - - .item-visibility { - margin-right: 0; - } - - .last-updated { - position: absolute; - right: 12px; - min-width: 250px; - text-align: right; - color: $gl-text-color-secondary; - } - } -} - .namespace-title { .tooltip-inner { max-width: 350px; } } - -ul.group-list-tree { - li.group-row { - > .group-row-contents .title { - line-height: $list-text-height; - } - - &.has-description > .group-row-contents .title { - line-height: inherit; - } - } -} - -.js-groups-list-holder { - .groups-list-loading { - font-size: 34px; - text-align: center; - } -} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 6ee8b33bd39..409b7285f82 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -18,6 +18,10 @@ .group-row { @include basic-list-stats; + + .description p { + margin-bottom: 0; + } } .ldap-group-links { @@ -237,3 +241,231 @@ overflow-y: unset; } } + +.groups-list-tree-container { + .has-no-search-results { + text-align: center; + padding: $gl-padding; + font-style: italic; + color: $well-light-text-color; + } + + > .group-list-tree > .group-row.has-children:first-child { + border-top: 0; + } +} + +.group-list-tree { + .avatar-container.content-loading { + position: relative; + + > a, + > a .avatar { + height: 100%; + border-radius: 50%; + } + + > a { + padding: 2px; + + .avatar { + border: 2px solid $white-normal; + + &.identicon { + line-height: 15px; + } + } + } + + &::after { + content: ""; + position: absolute; + height: 100%; + width: 100%; + background-color: transparent; + border: 2px outset $kdb-border; + border-radius: 50%; + animation: spin-avatar 3s infinite linear; + } + } + + .folder-toggle-wrap { + float: left; + line-height: $list-text-height; + font-size: 0; + + span { + font-size: $gl-font-size; + } + } + + .folder-caret, + .item-type-icon { + display: inline-block; + } + + .folder-caret { + width: 15px; + + svg { + margin-bottom: 2px; + } + } + + .item-type-icon { + margin-top: 2px; + width: 20px; + } + + > .group-row:not(.has-children) { + .folder-caret { + opacity: 0; + } + } + + .content-list li:last-child { + padding-bottom: 0; + } + + .group-list-tree { + margin-bottom: 0; + margin-left: 30px; + position: relative; + + &::before { + content: ''; + display: block; + width: 0; + position: absolute; + top: 5px; + bottom: 0; + left: -16px; + border-left: 2px solid $border-white-normal; + } + + .group-row { + position: relative; + + &::before { + content: ""; + display: block; + width: 10px; + height: 0; + border-top: 2px solid $border-white-normal; + position: absolute; + top: 30px; + left: -16px; + } + + &:last-child::before { + background: $white-light; + height: auto; + top: 30px; + bottom: 0; + } + + &.being-removed { + opacity: 0.5; + } + } + } + + .group-row { + padding: 0; + + &.has-children { + border-top: 0; + } + + &:first-child { + border-top: 1px solid $white-normal; + } + } + + .group-row-contents { + padding: $gl-padding-top; + + &:hover { + border-color: $row-hover-border; + background-color: $row-hover; + cursor: pointer; + } + + .avatar-container > a { + width: 100%; + text-decoration: none; + } + + &.has-more-items { + display: block; + padding: 20px 10px; + } + + .stats { + position: relative; + line-height: 46px; + + > span { + display: inline-flex; + align-items: center; + height: 16px; + min-width: 30px; + } + + > span:last-child { + margin-right: 0; + } + + .stat-value { + margin: 2px 0 0 5px; + } + } + + .controls { + margin-left: 5px; + + > .btn { + margin-right: $btn-xs-side-margin; + } + } + } + + .project-row-contents .stats { + line-height: inherit; + + > span:first-child { + margin-left: 25px; + } + + .item-visibility { + margin-right: 0; + } + + .last-updated { + position: absolute; + right: 12px; + min-width: 250px; + text-align: right; + color: $gl-text-color-secondary; + } + } +} + +ul.group-list-tree { + li.group-row { + > .group-row-contents .title { + line-height: $list-text-height; + } + + &.has-description > .group-row-contents .title { + line-height: inherit; + } + } +} + +.js-groups-list-holder { + .groups-list-loading { + font-size: 34px; + text-align: center; + } +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index b2dad4a358a..a8110f069d4 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -197,9 +197,21 @@ } &.assignee { - .author_link:hover { - .author { - text-decoration: underline; + .author_link { + display: block; + padding-left: 42px; + position: relative; + + &:hover { + .author { + text-decoration: underline; + } + } + + .avatar { + left: 0; + position: absolute; + top: 0; } } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 02803e7b040..1264d977b2f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -66,13 +66,9 @@ } } - .btn-group { - &.open { - .btn-default { - background-color: $white-normal; - border-color: $border-white-normal; - } - } + .btn-group.open .btn-default { + background-color: $white-normal; + border-color: $border-white-normal; } .btn .text-center { @@ -361,16 +357,14 @@ &:not(:first-child) { margin-left: 44px; - .left-connector { - &::before { - content: ''; - position: absolute; - top: 48%; - left: -44px; - border-top: 2px solid $border-color; - width: 44px; - height: 1px; - } + .left-connector::before { + content: ''; + position: absolute; + top: 48%; + left: -44px; + border-top: 2px solid $border-color; + width: 44px; + height: 1px; } } } @@ -386,22 +380,16 @@ &:last-child { .build { // Remove right connecting horizontal line from first build in last stage - &:first-child { - &::after { - border: 0; - } + &:first-child::after { + border: 0; } // Remove right curved connectors from all builds in last stage - &:not(:first-child) { - &::after { - border: 0; - } + &:not(:first-child)::after { + border: 0; } // Remove opposite curve - .curve { - &::before { - display: none; - } + .curve::before { + display: none; } } } @@ -409,16 +397,12 @@ &:first-child { .build { // Remove left curved connectors from all builds in first stage - &:not(:first-child) { - &::before { - border: 0; - } + &:not(:first-child)::before { + border: 0; } // Remove opposite curve - .curve { - &::after { - display: none; - } + .curve::after { + display: none; } } } diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 00457717f00..175d2779bb7 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -39,12 +39,15 @@ .ide-file-list { flex: 1; + padding-left: $gl-padding; + padding-right: $gl-padding; + padding-bottom: $grid-size; .file { cursor: pointer; &.file-open { - background: $link-active-background; + background: $white-normal; } &.file-active { @@ -84,12 +87,11 @@ .ide-new-btn { display: none; - margin-right: -8px; } &:hover, &:focus { - background: $link-active-background; + background: $white-normal; .ide-new-btn { display: block; @@ -111,12 +113,11 @@ } } -.file-name, -.file-col-commit-message { +.file-name { display: flex; overflow: visible; align-items: center; - padding: 6px 12px; + width: 100%; } .multi-file-loading-container { @@ -306,8 +307,18 @@ } .preview-container { - height: 100%; - overflow: auto; + flex-grow: 1; + position: relative; + + .md-previewer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: auto; + padding: $gl-padding; + } .file-container { background-color: $gray-darker; @@ -347,10 +358,6 @@ color: $diff-image-info-color; } } - - .md-previewer { - padding: $gl-padding; - } } .ide-mode-tabs { @@ -501,7 +508,7 @@ align-items: center; margin-bottom: 0; border-bottom: 1px solid $white-dark; - padding: $gl-btn-padding $gl-padding; + padding: 12px 0; } .multi-file-commit-panel-header-title { @@ -523,32 +530,31 @@ .multi-file-commit-list { flex: 1; overflow: auto; - padding: $gl-padding; + padding: $grid-size 0; + margin-left: -$grid-size; + margin-right: -$grid-size; min-height: 60px; + + .multi-file-commit-list-item { + margin-left: 0; + margin-right: 0; + } + + &.help-block { + margin-left: 0; + right: 0; + } } .multi-file-commit-list-item { - display: flex; - padding: 0; - align-items: center; - border-radius: $border-radius-default; - .multi-file-discard-btn { display: none; margin-top: -2px; margin-left: auto; - margin-right: $grid-size; color: $gl-link-color; - - &:focus, - &:hover { - text-decoration: underline; - } } &:hover { - background: $white-normal; - .multi-file-discard-btn { display: flex; } @@ -584,25 +590,39 @@ } } +.multi-file-commit-list-item, +.ide-file-list .file { + display: flex; + align-items: center; + margin-left: -$grid-size; + margin-right: -$grid-size; + padding: $grid-size / 2 $grid-size; + border-radius: $border-radius-default; + text-align: left; + + &:hover, + &:focus { + background: $white-normal; + } +} + .multi-file-commit-list-path { - padding: $grid-size / 2; - padding-left: $grid-size; + padding: 0; background: none; border: 0; text-align: left; width: 100%; - min-width: 0; + + &:hover, + &:focus { + outline: 0; + } svg { min-width: 16px; vertical-align: middle; display: inline-block; } - - &:hover, - &:focus { - outline: 0; - } } .multi-file-commit-list-file-path { @@ -619,12 +639,18 @@ .multi-file-commit-form { position: relative; - padding: $gl-padding; background-color: $white-light; - border-top: 1px solid $white-dark; border-left: 1px solid $white-dark; transition: all 0.3s ease; + > form, + > .commit-form-compact { + padding: $gl-padding 0; + margin-left: $gl-padding; + margin-right: $gl-padding; + border-top: 1px solid $white-dark; + } + .btn { font-size: $gl-font-size; } @@ -787,8 +813,9 @@ display: flex; flex: 1; flex-direction: column; - width: 100%; min-height: 140px; + margin-left: $gl-padding; + margin-right: $gl-padding; &.is-first { border-bottom: 1px solid $white-dark; @@ -979,9 +1006,8 @@ .ide-tree-header { display: flex; align-items: center; - padding: 10px 0; - margin-left: 10px; - margin-right: 10px; + margin-bottom: 8px; + padding: 12px 0; border-bottom: 1px solid $white-dark; .ide-new-btn { @@ -1012,9 +1038,9 @@ .commit-form-slide-up-enter-active, .commit-form-slide-up-leave-active { position: absolute; - top: 16px; - left: 16px; - right: 16px; + top: 0; + left: 0; + right: 0; transition: all 0.3s ease; } diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb index 7d7ff217e5d..09e143c23e8 100644 --- a/app/controllers/boards/issues_controller.rb +++ b/app/controllers/boards/issues_controller.rb @@ -94,7 +94,7 @@ module Boards def serialize_as_json(resource) resource.as_json( - only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position], + only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position, :weight], labels: true, issue_endpoints: true, include_full_project_path: board.group_board?, diff --git a/app/controllers/concerns/accepts_pending_invitations.rb b/app/controllers/concerns/accepts_pending_invitations.rb new file mode 100644 index 00000000000..6e8aef52b52 --- /dev/null +++ b/app/controllers/concerns/accepts_pending_invitations.rb @@ -0,0 +1,15 @@ +module AcceptsPendingInvitations + extend ActiveSupport::Concern + + def accept_pending_invitations + return unless resource.active_for_authentication? + + clear_stored_location_for_resource if resource.accept_pending_invitations!.any? + end + + def clear_stored_location_for_resource + session_key = stored_location_key_for(resource) + + session.delete(session_key) + end +end diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 6d9c38d9581..7bc46a6ccc0 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -1,4 +1,6 @@ class ConfirmationsController < Devise::ConfirmationsController + include AcceptsPendingInvitations + def almost_there flash[:notice] = nil render layout: "devise_empty" @@ -11,6 +13,8 @@ class ConfirmationsController < Devise::ConfirmationsController end def after_confirmation_path_for(resource_name, resource) + accept_pending_invitations + # incoming resource can either be a :user or an :email if signed_in?(:user) after_sign_in(resource) diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index f0e5d2aa94e..12a6cd11f80 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -23,7 +23,7 @@ class Profiles::KeysController < Profiles::ApplicationController def destroy @key = current_user.keys.find(params[:id]) - @key.destroy + Keys::DestroyService.new(current_user).execute(@key) respond_to do |format| format.html { redirect_to profile_keys_url, status: 302 } diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index f7417a6a5aa..6b40fc2fe68 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -18,19 +18,12 @@ class Projects::PipelinesController < Projects::ApplicationController .page(params[:page]) .per(30) - @running_count = PipelinesFinder - .new(project, scope: 'running').execute.count + @running_count = limited_pipelines_count(project, 'running') + @pending_count = limited_pipelines_count(project, 'pending') + @finished_count = limited_pipelines_count(project, 'finished') + @pipelines_count = limited_pipelines_count(project) - @pending_count = PipelinesFinder - .new(project, scope: 'pending').execute.count - - @finished_count = PipelinesFinder - .new(project, scope: 'finished').execute.count - - @pipelines_count = PipelinesFinder - .new(project).execute.count - - @pipelines.map(&:commit) # List commits for batch loading + Gitlab::Ci::Pipeline::Preloader.preload(@pipelines) respond_to do |format| format.html @@ -41,7 +34,7 @@ class Projects::PipelinesController < Projects::ApplicationController pipelines: PipelineSerializer .new(project: @project, current_user: @current_user) .with_pagination(request, response) - .represent(@pipelines), + .represent(@pipelines, disable_coverage: true), count: { all: @pipelines_count, running: @running_count, @@ -185,4 +178,10 @@ class Projects::PipelinesController < Projects::ApplicationController def authorize_update_pipeline! return access_denied! unless can?(current_user, :update_pipeline, @pipeline) end + + def limited_pipelines_count(project, scope = nil) + finder = PipelinesFinder.new(project, scope: scope) + + view_context.limited_counter_with_delimiter(finder.execute) + end end diff --git a/app/controllers/projects/settings/integrations_controller.rb b/app/controllers/projects/settings/integrations_controller.rb index 1ff08cce8cb..d9fecfecc40 100644 --- a/app/controllers/projects/settings/integrations_controller.rb +++ b/app/controllers/projects/settings/integrations_controller.rb @@ -11,7 +11,14 @@ module Projects @hook = ProjectHook.new # Services - @services = @project.find_or_initialize_services + @services = @project.find_or_initialize_services(exceptions: service_exceptions) + end + + private + + # Returns a list of services that should be hidden from the list + def service_exceptions + @project.disabled_services.dup end end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 1848c806c41..f5a222b3a48 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,5 +1,6 @@ class RegistrationsController < Devise::RegistrationsController include Recaptcha::Verify + include AcceptsPendingInvitations before_action :whitelist_query_limiting, only: [:destroy] @@ -16,6 +17,7 @@ class RegistrationsController < Devise::RegistrationsController end if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha + accept_pending_invitations super else flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' @@ -60,7 +62,7 @@ class RegistrationsController < Devise::RegistrationsController def after_sign_up_path_for(user) Gitlab::AppLogger.info("User Created: username=#{user.username} email=#{user.email} ip=#{request.remote_ip} confirmed:#{user.confirmed?}") - user.confirmed? ? dashboard_projects_path : users_almost_there_path + user.confirmed? ? stored_location_for(user) || dashboard_projects_path : users_almost_there_path end def after_inactive_sign_up_path_for(resource) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 61c10c427dd..d9649e30edc 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -184,7 +184,7 @@ module Ci end def playable? - action? && (manual? || complete?) + action? && (manual? || retryable?) end def action? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 1f49764e7cc..c26f0b6dcdc 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -406,7 +406,18 @@ module Ci end def has_warnings? - builds.latest.failed_but_allowed.any? + number_of_warnings.positive? + end + + def number_of_warnings + BatchLoader.for(id).batch(default_value: 0) do |pipeline_ids, loader| + Build.where(commit_id: pipeline_ids) + .latest + .failed_but_allowed + .group(:commit_id) + .count + .each { |id, amount| loader.call(id, amount) } + end end def set_config_source diff --git a/app/models/commit.rb b/app/models/commit.rb index b46f9f34689..56d4c86774e 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -224,8 +224,34 @@ class Commit Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message) end + def lazy_author + BatchLoader.for(author_email.downcase).batch do |emails, loader| + # A Hash that maps user Emails to the corresponding User objects. The + # Emails at this point are the _primary_ Emails of the Users. + users_for_emails = User + .by_any_email(emails) + .each_with_object({}) { |user, hash| hash[user.email] = user } + + users_for_ids = users_for_emails + .values + .each_with_object({}) { |user, hash| hash[user.id] = user } + + # Some commits may have used an alternative Email address. In this case we + # need to query the "emails" table to map those addresses to User objects. + Email + .where(email: emails - users_for_emails.keys) + .pluck(:email, :user_id) + .each { |(email, id)| users_for_emails[email] = users_for_ids[id] } + + users_for_emails.each { |email, user| loader.call(email, user) } + end + end + def author - User.find_by_any_email(author_email.downcase) + # We use __sync so that we get the actual objects back (including an actual + # nil), instead of a wrapper, as returning a wrapped nil breaks a lot of + # code. + lazy_author.__sync end request_cache(:author) { author_email.downcase } diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 97d89422594..a7d05722287 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -2,6 +2,7 @@ class CommitStatus < ActiveRecord::Base include HasStatus include Importable include AfterCommitQueue + include Presentable self.table_name = 'ci_builds' diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index cefa5c13c5f..db7254c27e0 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -12,8 +12,8 @@ module Sortable scope :order_created_asc, -> { reorder(created_at: :asc) } scope :order_updated_desc, -> { reorder(updated_at: :desc) } scope :order_updated_asc, -> { reorder(updated_at: :asc) } - scope :order_name_asc, -> { reorder(name: :asc) } - scope :order_name_desc, -> { reorder(name: :desc) } + scope :order_name_asc, -> { reorder("lower(name) asc") } + scope :order_name_desc, -> { reorder("lower(name) desc") } end module ClassMethods diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb index 73fc5048dcf..1caf47072bc 100644 --- a/app/models/concerns/time_trackable.rb +++ b/app/models/concerns/time_trackable.rb @@ -53,6 +53,10 @@ module TimeTrackable Gitlab::TimeTrackingFormatter.output(time_estimate) end + def time_estimate=(val) + val.is_a?(Integer) ? super([val, Gitlab::Database::MAX_INT_VALUE].min) : super(val) + end + private def touchable? diff --git a/app/models/project.rb b/app/models/project.rb index 0975e64e995..35c873830a7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -997,7 +997,7 @@ class Project < ActiveRecord::Base available_services_names = Service.available_services_names - exceptions - available_services_names.map do |service_name| + available_services = available_services_names.map do |service_name| service = find_service(services, service_name) if service @@ -1014,6 +1014,14 @@ class Project < ActiveRecord::Base end end end + + available_services.reject do |service| + disabled_services.include?(service.to_param) + end + end + + def disabled_services + [] end def find_or_initialize_service(name) diff --git a/app/models/user.rb b/app/models/user.rb index 474fde36c02..8ef3c3ceff0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -860,6 +860,16 @@ class User < ActiveRecord::Base confirmed? && !temp_oauth_email? end + def accept_pending_invitations! + pending_invitations.select do |member| + member.accept_invite!(self) + end + end + + def pending_invitations + Member.where(invite_email: verified_emails).invite + end + def all_emails all_emails = [] all_emails << email unless temp_oauth_email? diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb index 4873d7ce662..e0aaa5cb736 100644 --- a/app/presenters/ci/build_presenter.rb +++ b/app/presenters/ci/build_presenter.rb @@ -1,16 +1,5 @@ module Ci - class BuildPresenter < Gitlab::View::Presenter::Delegated - CALLOUT_FAILURE_MESSAGES = { - unknown_failure: 'There is an unknown failure, please try again', - script_failure: 'There has been a script failure. Check the job log for more information', - api_failure: 'There has been an API failure, please try again', - stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again', - runner_system_failure: 'There has been a runner system failure, please try again', - missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information' - }.freeze - - presents :build - + class BuildPresenter < CommitStatusPresenter def erased_by_user? # Build can be erased through API, therefore it does not have # `erased_by` user assigned in that case. @@ -44,14 +33,6 @@ module Ci "#{subject.name} - #{detailed_status.status_tooltip}" end - def callout_failure_message - CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym] - end - - def recoverable? - failed? && !unrecoverable? - end - private def tooltip_for_badge @@ -61,9 +42,5 @@ module Ci def detailed_status @detailed_status ||= subject.detailed_status(user) end - - def unrecoverable? - script_failure? || missing_dependency_failure? - end end end diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb new file mode 100644 index 00000000000..c7f7aa836bd --- /dev/null +++ b/app/presenters/commit_status_presenter.rb @@ -0,0 +1,24 @@ +class CommitStatusPresenter < Gitlab::View::Presenter::Delegated + CALLOUT_FAILURE_MESSAGES = { + unknown_failure: 'There is an unknown failure, please try again', + script_failure: 'There has been a script failure. Check the job log for more information', + api_failure: 'There has been an API failure, please try again', + stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again', + runner_system_failure: 'There has been a runner system failure, please try again', + missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information' + }.freeze + + presents :build + + def callout_failure_message + CALLOUT_FAILURE_MESSAGES[failure_reason.to_sym] + end + + def recoverable? + failed? && !unrecoverable? + end + + def unrecoverable? + script_failure? || missing_dependency_failure? + end +end diff --git a/app/presenters/generic_commit_status_presenter.rb b/app/presenters/generic_commit_status_presenter.rb new file mode 100644 index 00000000000..da09df29a37 --- /dev/null +++ b/app/presenters/generic_commit_status_presenter.rb @@ -0,0 +1,2 @@ +class GenericCommitStatusPresenter < CommitStatusPresenter +end diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 6457294b285..f782b411b84 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -4,7 +4,11 @@ class PipelineEntity < Grape::Entity expose :id expose :user, using: UserEntity expose :active?, as: :active - expose :coverage + + # Coverage isn't always necessary (e.g. when displaying project pipelines in + # the UI). Instead of creating an entirely different entity we just allow the + # disabling of this specific field whenever necessary. + expose :coverage, unless: proc { options[:disable_coverage] } expose :source expose :created_at, :updated_at diff --git a/app/services/keys/base_service.rb b/app/services/keys/base_service.rb index f78791932a7..df8e82f5f60 100644 --- a/app/services/keys/base_service.rb +++ b/app/services/keys/base_service.rb @@ -2,7 +2,7 @@ module Keys class BaseService attr_accessor :user, :params - def initialize(user, params) + def initialize(user, params = {}) @user, @params = user, params @ip_address = @params.delete(:ip_address) end diff --git a/app/services/keys/destroy_service.rb b/app/services/keys/destroy_service.rb new file mode 100644 index 00000000000..785cfa3a1d8 --- /dev/null +++ b/app/services/keys/destroy_service.rb @@ -0,0 +1,12 @@ +module Keys + class DestroyService < ::Keys::BaseService + def execute(key) + key.destroy if destroy_possible?(key) + end + + # overriden in EE::Keys::DestroyService + def destroy_possible?(key) + true + end + end +end diff --git a/app/services/lfs/unlock_file_service.rb b/app/services/lfs/unlock_file_service.rb index 6c93dc69bb0..7eb89339a92 100644 --- a/app/services/lfs/unlock_file_service.rb +++ b/app/services/lfs/unlock_file_service.rb @@ -2,14 +2,14 @@ module Lfs class UnlockFileService < BaseService def execute unless can?(current_user, :push_code, project) - raise Gitlab::GitAccess::UnauthorizedError, 'You have no permissions' + raise Gitlab::GitAccess::UnauthorizedError, _('You have no permissions') end unlock_file rescue Gitlab::GitAccess::UnauthorizedError => ex error(ex.message, 403) rescue ActiveRecord::RecordNotFound - error('Lock not found', 404) + error(_('Lock not found'), 404) rescue => ex error(ex.message, 500) end @@ -24,9 +24,9 @@ module Lfs success(lock: lock, http_status: :ok) elsif forced - error('You must have master access to force delete a lock', 403) + error(_('You must have master access to force delete a lock'), 403) else - error("#{lock.path} is locked by GitLab User #{lock.user_id}", 403) + error(_("%{lock_path} is locked by GitLab User %{lock_user_id}") % { lock_path: lock.path, lock_user_id: lock.user_id }, 403) end end diff --git a/app/services/milestones/base_service.rb b/app/services/milestones/base_service.rb index 4963601ea8b..cce0863d611 100644 --- a/app/services/milestones/base_service.rb +++ b/app/services/milestones/base_service.rb @@ -5,6 +5,7 @@ module Milestones def initialize(parent, user, params = {}) @parent, @current_user, @params = parent, user, params.dup + super end end end diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 1c5b4aecabb..2244d16f0a6 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -121,7 +121,7 @@ %tr %td.shortcut .key g - .key e + .key v %td Go to the project's activity feed %tr @@ -175,6 +175,18 @@ %tr %td.shortcut .key g + .key e + %td + Go to environments + %tr + %td.shortcut + .key g + .key k + %td + Go to kubernetes + %tr + %td.shortcut + .key g .key s %td Go to snippets diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index c3ea592a6b5..0023ede2be9 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -212,7 +212,7 @@ - if project_nav_tab? :clusters - show_cluster_hint = show_gke_cluster_integration_callout?(@project) = nav_link(controller: [:clusters, :user, :gcp]) do - = link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-cluster' do + = link_to project_clusters_path(@project), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do %span = _('Kubernetes') - if show_cluster_hint diff --git a/app/views/notify/member_invited_email.html.haml b/app/views/notify/member_invited_email.html.haml index b8b75da3f2f..6730172242b 100644 --- a/app/views/notify/member_invited_email.html.haml +++ b/app/views/notify/member_invited_email.html.haml @@ -4,7 +4,7 @@ by = link_to member.created_by.name, user_url(member.created_by) to join the - = link_to member_source.human_name, member_source.web_url + = link_to member_source.human_name, member_source.public? ? member_source.web_url : invite_url(@token) #{member_source.model_name.singular} as #{member.human_access}. %p |