Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorKamil Trzciński <ayufan@ayufan.eu>2018-04-06 12:16:40 +0300
committerKamil Trzciński <ayufan@ayufan.eu>2018-04-06 12:16:40 +0300
commit2bf8345542dbdae17c3755ca7157cbaf70ffde41 (patch)
tree48b9b82e6ae9ed2e12649b4ea4f95b6833948d3a /app
parent99edc15127d9d23475c94079d53e2893f58c042a (diff)
parent20e9b32c96a5b08e3f61c8974b43977f98d8666e (diff)
Merge branch 'master' into '42568-pipeline-empty-state'
# Conflicts: # app/views/projects/jobs/show.html.haml # lib/gitlab/ci/status/core.rb
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/services/board_service.js2
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js18
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js3
-rw-r--r--app/assets/javascripts/ide/components/ide.vue5
-rw-r--r--app/assets/javascripts/ide/components/ide_file_buttons.vue83
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue67
-rw-r--r--app/assets/javascripts/ide/components/repo_file_buttons.vue61
-rw-r--r--app/assets/javascripts/ide/components/resizable_panel.vue109
-rw-r--r--app/assets/javascripts/ide/lib/editor.js20
-rw-r--r--app/assets/javascripts/ide/lib/editor_options.js2
-rw-r--r--app/assets/javascripts/ide/stores/actions/file.js4
-rw-r--r--app/assets/javascripts/ide/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js6
-rw-r--r--app/assets/javascripts/ide/stores/utils.js8
-rw-r--r--app/assets/javascripts/ide/stores/workers/files_decorator_worker.js23
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_details_block.vue2
-rw-r--r--app/assets/javascripts/milestone_select.js8
-rw-r--r--app/assets/javascripts/notes.js4
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue4
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js1
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js1
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_component.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue43
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js23
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue90
-rw-r--r--app/assets/stylesheets/framework/lists.scss13
-rw-r--r--app/assets/stylesheets/framework/typography.scss5
-rw-r--r--app/assets/stylesheets/pages/builds.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss1
-rw-r--r--app/assets/stylesheets/pages/note_form.scss13
-rw-r--r--app/assets/stylesheets/pages/notes.scss16
-rw-r--r--app/assets/stylesheets/pages/pages.scss60
-rw-r--r--app/assets/stylesheets/pages/profile.scss7
-rw-r--r--app/assets/stylesheets/pages/repo.scss28
-rw-r--r--app/controllers/projects/discussions_controller.rb4
-rw-r--r--app/controllers/projects/git_http_client_controller.rb1
-rw-r--r--app/controllers/projects/git_http_controller.rb2
-rw-r--r--app/controllers/projects/jobs_controller.rb10
-rw-r--r--app/controllers/projects/labels_controller.rb3
-rw-r--r--app/controllers/projects/lfs_api_controller.rb2
-rw-r--r--app/controllers/projects/lfs_storage_controller.rb13
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb37
-rw-r--r--app/controllers/projects/refs_controller.rb2
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb50
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/finders/labels_finder.rb35
-rw-r--r--app/helpers/boards_helper.rb6
-rw-r--r--app/helpers/labels_helper.rb10
-rw-r--r--app/helpers/services_helper.rb25
-rw-r--r--app/helpers/tree_helper.rb2
-rw-r--r--app/models/appearance.rb2
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/ci/job_artifact.rb5
-rw-r--r--app/models/commit.rb3
-rw-r--r--app/models/concerns/chronic_duration_attribute.rb6
-rw-r--r--app/models/concerns/presentable.rb8
-rw-r--r--app/models/hooks/project_hook.rb1
-rw-r--r--app/models/note.rb8
-rw-r--r--app/models/project.rb28
-rw-r--r--app/models/project_services/chat_notification_service.rb14
-rw-r--r--app/models/project_services/hipchat_service.rb2
-rw-r--r--app/models/service.rb8
-rw-r--r--app/models/user.rb9
-rw-r--r--app/policies/issuable_policy.rb14
-rw-r--r--app/policies/issue_policy.rb2
-rw-r--r--app/policies/merge_request_policy.rb1
-rw-r--r--app/policies/project_policy.rb35
-rw-r--r--app/presenters/ci/build_presenter.rb16
-rw-r--r--app/serializers/build_metadata_entity.rb5
-rw-r--r--app/serializers/status_entity.rb2
-rw-r--r--app/services/boards/issues/move_service.rb5
-rw-r--r--app/services/boards/lists/create_service.rb8
-rw-r--r--app/services/ci/register_job_service.rb19
-rw-r--r--app/services/issuable/destroy_service.rb1
-rw-r--r--app/services/issuable_base_service.rb2
-rw-r--r--app/services/issues/update_service.rb17
-rw-r--r--app/services/notes/post_process_service.rb10
-rw-r--r--app/services/projects/autocomplete_service.rb3
-rw-r--r--app/services/projects/gitlab_projects_import_service.rb5
-rw-r--r--app/services/projects/import_export/export_service.rb6
-rw-r--r--app/services/quick_actions/interpret_service.rb5
-rw-r--r--app/services/system_note_service.rb4
-rw-r--r--app/uploaders/job_artifact_uploader.rb4
-rw-r--r--app/uploaders/legacy_artifact_uploader.rb4
-rw-r--r--app/uploaders/object_storage.rb70
-rw-r--r--app/views/admin/application_settings/_email.html.haml26
-rw-r--r--app/views/admin/application_settings/_form.html.haml173
-rw-r--r--app/views/admin/application_settings/_gitaly.html.haml27
-rw-r--r--app/views/admin/application_settings/_koding.html.haml24
-rw-r--r--app/views/admin/application_settings/_performance.html.haml19
-rw-r--r--app/views/admin/application_settings/_plantuml.html.haml20
-rw-r--r--app/views/admin/application_settings/_realtime.html.haml19
-rw-r--r--app/views/admin/application_settings/_registry.html.haml10
-rw-r--r--app/views/admin/application_settings/_terminal.html.haml13
-rw-r--r--app/views/admin/application_settings/_usage.html.haml37
-rw-r--r--app/views/admin/application_settings/show.html.haml108
-rw-r--r--app/views/ci/status/_badge.html.haml4
-rw-r--r--app/views/ci/status/_dropdown_graph_badge.html.haml7
-rw-r--r--app/views/email_rejection_mailer/rejection.text.haml3
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml6
-rw-r--r--app/views/projects/_export.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml4
-rw-r--r--app/views/projects/commits/_commit.atom.builder2
-rw-r--r--app/views/projects/jobs/_sidebar.html.haml8
-rw-r--r--app/views/projects/jobs/show.html.haml2
-rw-r--r--app/views/projects/pages/_list.html.haml37
-rw-r--r--app/views/projects/pages/show.html.haml3
-rw-r--r--app/views/projects/pages_domains/edit.html.haml2
-rw-r--r--app/views/projects/pages_domains/new.html.haml2
-rw-r--r--app/views/projects/pages_domains/show.html.haml54
-rw-r--r--app/views/projects/settings/ci_cd/_badge.html.haml (renamed from app/views/projects/pipelines_settings/_badge.html.haml)0
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml (renamed from app/views/projects/pipelines_settings/_show.html.haml)14
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml5
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/notes/_form.html.haml35
-rw-r--r--app/views/shared/web_hooks/_form.html.haml7
-rw-r--r--app/workers/new_note_worker.rb2
117 files changed, 1302 insertions, 654 deletions
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index d78d4701974..7c90597f77c 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -19,7 +19,7 @@ export default class BoardService {
}
static generateIssuePath(boardId, id) {
- return `${gon.relative_url_root}/-/boards/${boardId ? `/${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
+ return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
}
all() {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index e6390f0855b..d7e1de18d09 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -26,8 +26,8 @@ export default class FilteredSearchDropdownManager {
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.groupsOnly = isGroup;
- this.groupAncestor = isGroupAncestor;
- this.isGroupDecendent = isGroupDecendent;
+ this.includeAncestorGroups = isGroupAncestor;
+ this.includeDescendantGroups = isGroupDecendent;
this.setupMapping();
@@ -108,7 +108,19 @@ export default class FilteredSearchDropdownManager {
}
getLabelsEndpoint() {
- const endpoint = `${this.baseEndpoint}/labels.json`;
+ let endpoint = `${this.baseEndpoint}/labels.json?`;
+
+ if (this.groupsOnly) {
+ endpoint = `${endpoint}only_group_labels=true&`;
+ }
+
+ if (this.includeAncestorGroups) {
+ endpoint = `${endpoint}include_ancestor_groups=true&`;
+ }
+
+ if (this.includeDescendantGroups) {
+ endpoint = `${endpoint}include_descendant_groups=true`;
+ }
return endpoint;
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 71b7e80335b..cf5ba1e1771 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -21,7 +21,7 @@ export default class FilteredSearchManager {
constructor({
page,
isGroup = false,
- isGroupAncestor = false,
+ isGroupAncestor = true,
isGroupDecendent = false,
filteredSearchTokenKeys = FilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
@@ -86,6 +86,7 @@ export default class FilteredSearchManager {
page: this.page,
isGroup: this.isGroup,
isGroupAncestor: this.isGroupAncestor,
+ isGroupDecendent: this.isGroupDecendent,
filteredSearchTokenKeys: this.filteredSearchTokenKeys,
});
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue
index d22869466c9..1c237c0ec97 100644
--- a/app/assets/javascripts/ide/components/ide.vue
+++ b/app/assets/javascripts/ide/components/ide.vue
@@ -3,7 +3,6 @@ import { mapState, mapGetters } from 'vuex';
import ideSidebar from './ide_side_bar.vue';
import ideContextbar from './ide_context_bar.vue';
import repoTabs from './repo_tabs.vue';
-import repoFileButtons from './repo_file_buttons.vue';
import ideStatusBar from './ide_status_bar.vue';
import repoEditor from './repo_editor.vue';
@@ -12,7 +11,6 @@ export default {
ideSidebar,
ideContextbar,
repoTabs,
- repoFileButtons,
ideStatusBar,
repoEditor,
},
@@ -70,9 +68,6 @@ export default {
class="multi-file-edit-pane-content"
:file="activeFile"
/>
- <repo-file-buttons
- :file="activeFile"
- />
<ide-status-bar
:file="activeFile"
/>
diff --git a/app/assets/javascripts/ide/components/ide_file_buttons.vue b/app/assets/javascripts/ide/components/ide_file_buttons.vue
new file mode 100644
index 00000000000..6d07329df71
--- /dev/null
+++ b/app/assets/javascripts/ide/components/ide_file_buttons.vue
@@ -0,0 +1,83 @@
+<script>
+import { __ } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ showButtons() {
+ return (
+ this.file.rawPath || this.file.blamePath || this.file.commitsPath || this.file.permalink
+ );
+ },
+ rawDownloadButtonLabel() {
+ return this.file.binary ? __('Download') : __('Raw');
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ v-if="showButtons"
+ class="pull-right ide-btn-group"
+ >
+ <a
+ v-tooltip
+ :href="file.blamePath"
+ :title="__('Blame')"
+ class="btn btn-xs btn-transparent blame"
+ >
+ <icon
+ name="blame"
+ :size="16"
+ />
+ </a>
+ <a
+ v-tooltip
+ :href="file.commitsPath"
+ :title="__('History')"
+ class="btn btn-xs btn-transparent history"
+ >
+ <icon
+ name="history"
+ :size="16"
+ />
+ </a>
+ <a
+ v-tooltip
+ :href="file.permalink"
+ :title="__('Permalink')"
+ class="btn btn-xs btn-transparent permalink"
+ >
+ <icon
+ name="link"
+ :size="16"
+ />
+ </a>
+ <a
+ v-tooltip
+ :href="file.rawPath"
+ target="_blank"
+ class="btn btn-xs btn-transparent prepend-left-10 raw"
+ rel="noopener noreferrer"
+ :title="rawDownloadButtonLabel">
+ <icon
+ name="download"
+ :size="16"
+ />
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index b1a16350c19..8a709b31ea0 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -2,10 +2,16 @@
/* global monaco */
import { mapState, mapGetters, mapActions } from 'vuex';
import flash from '~/flash';
+import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor';
+import IdeFileButtons from './ide_file_buttons.vue';
export default {
+ components: {
+ ContentViewer,
+ IdeFileButtons,
+ },
props: {
file: {
type: Object,
@@ -13,11 +19,21 @@ export default {
},
},
computed: {
- ...mapState(['leftPanelCollapsed', 'rightPanelCollapsed', 'viewer', 'delayViewerUpdated']),
+ ...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']),
...mapGetters(['currentMergeRequest']),
shouldHideEditor() {
return this.file && this.file.binary && !this.file.raw;
},
+ editTabCSS() {
+ return {
+ active: this.file.viewMode === 'edit',
+ };
+ },
+ previewTabCSS() {
+ return {
+ active: this.file.viewMode === 'preview',
+ };
+ },
},
watch: {
file(oldVal, newVal) {
@@ -26,15 +42,17 @@ export default {
this.initMonaco();
}
},
- leftPanelCollapsed() {
- this.editor.updateDimensions();
- },
rightPanelCollapsed() {
this.editor.updateDimensions();
},
viewer() {
this.createEditorInstance();
},
+ panelResizing() {
+ if (!this.panelResizing) {
+ this.editor.updateDimensions();
+ }
+ },
},
beforeDestroy() {
this.editor.dispose();
@@ -56,6 +74,7 @@ export default {
'changeFileContent',
'setFileLanguage',
'setEditorPosition',
+ 'setFileViewMode',
'setFileEOL',
'updateViewer',
'updateDelayViewerUpdated',
@@ -153,15 +172,47 @@ export default {
class="blob-viewer-container blob-editor-container"
>
<div
- v-if="shouldHideEditor"
- v-html="file.html"
- >
+ class="ide-mode-tabs clearfix"
+ v-if="!shouldHideEditor">
+ <ul class="nav-links pull-left">
+ <li :class="editTabCSS">
+ <a
+ href="javascript:void(0);"
+ role="button"
+ @click.prevent="setFileViewMode({ file, viewMode: 'edit' })">
+ <template v-if="viewer === 'editor'">
+ {{ __('Edit') }}
+ </template>
+ <template v-else>
+ {{ __('Review') }}
+ </template>
+ </a>
+ </li>
+ <li
+ v-if="file.previewMode"
+ :class="previewTabCSS">
+ <a
+ href="javascript:void(0);"
+ role="button"
+ @click.prevent="setFileViewMode({ file, viewMode:'preview' })">
+ {{ file.previewMode.previewTitle }}
+ </a>
+ </li>
+ </ul>
+ <ide-file-buttons
+ :file="file"
+ />
</div>
<div
- v-show="!shouldHideEditor"
+ v-show="!shouldHideEditor && file.viewMode === 'edit'"
ref="editor"
class="multi-file-editor-holder"
>
</div>
+ <content-viewer
+ v-if="!shouldHideEditor && file.viewMode === 'preview'"
+ :content="file.content || file.raw"
+ :path="file.path"
+ :project-path="file.projectId"/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/repo_file_buttons.vue b/app/assets/javascripts/ide/components/repo_file_buttons.vue
deleted file mode 100644
index 4ea8cf7504b..00000000000
--- a/app/assets/javascripts/ide/components/repo_file_buttons.vue
+++ /dev/null
@@ -1,61 +0,0 @@
-<script>
-export default {
- props: {
- file: {
- type: Object,
- required: true,
- },
- },
- computed: {
- showButtons() {
- return this.file.rawPath ||
- this.file.blamePath ||
- this.file.commitsPath ||
- this.file.permalink;
- },
- rawDownloadButtonLabel() {
- return this.file.binary ? 'Download' : 'Raw';
- },
- },
-};
-</script>
-
-<template>
- <div
- v-if="showButtons"
- class="multi-file-editor-btn-group"
- >
- <a
- :href="file.rawPath"
- target="_blank"
- class="btn btn-default btn-sm raw"
- rel="noopener noreferrer">
- {{ rawDownloadButtonLabel }}
- </a>
-
- <div
- class="btn-group"
- role="group"
- aria-label="File actions"
- >
- <a
- :href="file.blamePath"
- class="btn btn-default btn-sm blame"
- >
- Blame
- </a>
- <a
- :href="file.commitsPath"
- class="btn btn-default btn-sm history"
- >
- History
- </a>
- <a
- :href="file.permalink"
- class="btn btn-default btn-sm permalink"
- >
- Permalink
- </a>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/ide/components/resizable_panel.vue b/app/assets/javascripts/ide/components/resizable_panel.vue
index faa690ecba0..5ea2a2f6825 100644
--- a/app/assets/javascripts/ide/components/resizable_panel.vue
+++ b/app/assets/javascripts/ide/components/resizable_panel.vue
@@ -1,67 +1,64 @@
<script>
- import { mapActions, mapState } from 'vuex';
- import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
+import { mapActions, mapState } from 'vuex';
+import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
- export default {
- components: {
- PanelResizer,
+export default {
+ components: {
+ PanelResizer,
+ },
+ props: {
+ collapsible: {
+ type: Boolean,
+ required: true,
},
- props: {
- collapsible: {
- type: Boolean,
- required: true,
- },
- initialWidth: {
- type: Number,
- required: true,
- },
- minSize: {
- type: Number,
- required: false,
- default: 200,
- },
- side: {
- type: String,
- required: true,
- },
+ initialWidth: {
+ type: Number,
+ required: true,
},
- data() {
- return {
- width: this.initialWidth,
- };
+ minSize: {
+ type: Number,
+ required: false,
+ default: 340,
},
- computed: {
- ...mapState({
- collapsed(state) {
- return state[`${this.side}PanelCollapsed`];
- },
- }),
- panelStyle() {
- if (!this.collapsed) {
- return {
- width: `${this.width}px`,
- };
- }
-
- return {};
- },
+ side: {
+ type: String,
+ required: true,
},
- methods: {
- ...mapActions([
- 'setPanelCollapsedStatus',
- 'setResizingStatus',
- ]),
- toggleFullbarCollapsed() {
- if (this.collapsed && this.collapsible) {
- this.setPanelCollapsedStatus({
- side: this.side,
- collapsed: !this.collapsed,
- });
- }
+ },
+ data() {
+ return {
+ width: this.initialWidth,
+ };
+ },
+ computed: {
+ ...mapState({
+ collapsed(state) {
+ return state[`${this.side}PanelCollapsed`];
},
+ }),
+ panelStyle() {
+ if (!this.collapsed) {
+ return {
+ width: `${this.width}px`,
+ };
+ }
+
+ return {};
+ },
+ },
+ methods: {
+ ...mapActions(['setPanelCollapsedStatus', 'setResizingStatus']),
+ toggleFullbarCollapsed() {
+ if (this.collapsed && this.collapsible) {
+ this.setPanelCollapsedStatus({
+ side: this.side,
+ collapsed: !this.collapsed,
+ });
+ }
},
- maxSize: (window.innerWidth / 2),
- };
+ },
+ maxSize: window.innerWidth / 2,
+};
</script>
<template>
diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js
index 6b4ba30e086..001737d6ee8 100644
--- a/app/assets/javascripts/ide/lib/editor.js
+++ b/app/assets/javascripts/ide/lib/editor.js
@@ -69,6 +69,7 @@ export default class Editor {
occurrencesHighlight: false,
renderLineHighlight: 'none',
hideCursorInOverviewRuler: true,
+ renderSideBySide: Editor.renderSideBySide(domElement),
})),
);
@@ -81,7 +82,7 @@ export default class Editor {
}
attachModel(model) {
- if (this.instance.getEditorType() === 'vs.editor.IDiffEditor') {
+ if (this.isDiffEditorType) {
this.instance.setModel({
original: model.getOriginalModel(),
modified: model.getModel(),
@@ -153,6 +154,7 @@ export default class Editor {
updateDimensions() {
this.instance.layout();
+ this.updateDiffView();
}
setPosition({ lineNumber, column }) {
@@ -171,4 +173,20 @@ export default class Editor {
this.disposable.add(this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)));
}
+
+ updateDiffView() {
+ if (!this.isDiffEditorType) return;
+
+ this.instance.updateOptions({
+ renderSideBySide: Editor.renderSideBySide(this.instance.getDomNode()),
+ });
+ }
+
+ get isDiffEditorType() {
+ return this.instance.getEditorType() === 'vs.editor.IDiffEditor';
+ }
+
+ static renderSideBySide(domElement) {
+ return domElement.offsetWidth >= 700;
+ }
}
diff --git a/app/assets/javascripts/ide/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js
index a213862f9b3..9f895d49f2e 100644
--- a/app/assets/javascripts/ide/lib/editor_options.js
+++ b/app/assets/javascripts/ide/lib/editor_options.js
@@ -6,7 +6,7 @@ export const defaultEditorOptions = {
minimap: {
enabled: false,
},
- wordWrap: 'bounded',
+ wordWrap: 'on',
};
export default [
diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js
index 6b034ea1e82..1a17320a1ea 100644
--- a/app/assets/javascripts/ide/stores/actions/file.js
+++ b/app/assets/javascripts/ide/stores/actions/file.js
@@ -149,6 +149,10 @@ export const setEditorPosition = ({ getters, commit }, { editorRow, editorColumn
}
};
+export const setFileViewMode = ({ state, commit }, { file, viewMode }) => {
+ commit(types.SET_FILE_VIEWMODE, { file, viewMode });
+};
+
export const discardFileChanges = ({ state, commit }, path) => {
const file = state.entries[path];
diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js
index ee759bff516..e3f504e5ab0 100644
--- a/app/assets/javascripts/ide/stores/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/mutation_types.js
@@ -38,6 +38,7 @@ export const SET_FILE_BASE_RAW_DATA = 'SET_FILE_BASE_RAW_DATA';
export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
export const SET_FILE_POSITION = 'SET_FILE_POSITION';
+export const SET_FILE_VIEWMODE = 'SET_FILE_VIEWMODE';
export const SET_FILE_EOL = 'SET_FILE_EOL';
export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES';
export const ADD_FILE_TO_CHANGED = 'ADD_FILE_TO_CHANGED';
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index 926b6f66d78..6a143e518f9 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -42,6 +42,7 @@ export default {
renderError: data.render_error,
raw: null,
baseRaw: null,
+ html: data.html,
});
},
[types.SET_FILE_RAW_DATA](state, { file, raw }) {
@@ -83,6 +84,11 @@ export default {
mrChange,
});
},
+ [types.SET_FILE_VIEWMODE](state, { file, viewMode }) {
+ Object.assign(state.entries[file.path], {
+ viewMode,
+ });
+ },
[types.DISCARD_FILE_CHANGES](state, path) {
Object.assign(state.entries[path], {
content: state.entries[path].raw,
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 63e4de3b17d..4befcc501ef 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -38,6 +38,8 @@ export const dataStructure = () => ({
editorColumn: 1,
fileLanguage: '',
eol: '',
+ viewMode: 'edit',
+ previewMode: null,
});
export const decorateData = entity => {
@@ -57,8 +59,9 @@ export const decorateData = entity => {
changed = false,
parentTreeUrl = '',
base64 = false,
-
+ previewMode,
file_lock,
+ html,
} = entity;
return {
@@ -79,8 +82,9 @@ export const decorateData = entity => {
renderError,
content,
base64,
-
+ previewMode,
file_lock,
+ html,
};
};
diff --git a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js
index a4cd1ab099f..a1673276900 100644
--- a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js
+++ b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js
@@ -1,14 +1,8 @@
+import { viewerInformationForPath } from '~/vue_shared/components/content_viewer/lib/viewer_utils';
import { decorateData, sortTree } from '../utils';
self.addEventListener('message', e => {
- const {
- data,
- projectId,
- branchId,
- tempFile = false,
- content = '',
- base64 = false,
- } = e.data;
+ const { data, projectId, branchId, tempFile = false, content = '', base64 = false } = e.data;
const treeList = [];
let file;
@@ -19,9 +13,7 @@ self.addEventListener('message', e => {
if (pathSplit.length > 0) {
pathSplit.reduce((pathAcc, folderName) => {
const parentFolder = acc[pathAcc[pathAcc.length - 1]];
- const folderPath = `${
- parentFolder ? `${parentFolder.path}/` : ''
- }${folderName}`;
+ const folderPath = `${parentFolder ? `${parentFolder.path}/` : ''}${folderName}`;
const foundEntry = acc[folderPath];
if (!foundEntry) {
@@ -33,9 +25,7 @@ self.addEventListener('message', e => {
path: folderPath,
url: `/${projectId}/tree/${branchId}/${folderPath}/`,
type: 'tree',
- parentTreeUrl: parentFolder
- ? parentFolder.url
- : `/${projectId}/tree/${branchId}/`,
+ parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`,
tempFile,
changed: tempFile,
opened: tempFile,
@@ -70,13 +60,12 @@ self.addEventListener('message', e => {
path,
url: `/${projectId}/blob/${branchId}/${path}`,
type: 'blob',
- parentTreeUrl: fileFolder
- ? fileFolder.url
- : `/${projectId}/blob/${branchId}`,
+ parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`,
tempFile,
changed: tempFile,
content,
base64,
+ previewMode: viewerInformationForPath(blobName),
});
Object.assign(acc, {
diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
index 172de6b3679..af47056d98f 100644
--- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue
@@ -45,7 +45,7 @@
return `#${this.job.runner.id}`;
},
hasTimeout() {
- return this.job.metadata != null && this.job.metadata.timeout_human_readable !== '';
+ return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
},
timeout() {
if (this.job.metadata == null) {
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index add07c156a4..c749042a14a 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -94,10 +94,10 @@ export default class MilestoneSelect {
if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove();
}
- $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
+ $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`).addClass('is-active');
}),
renderRow: milestone => `
- <li data-milestone-id="${milestone.name}">
+ <li data-milestone-id="${_.escape(milestone.name)}">
<a href='#' class='dropdown-menu-milestone-link'>
${_.escape(milestone.title)}
</a>
@@ -125,7 +125,6 @@ export default class MilestoneSelect {
return milestone.id;
}
},
- isSelected: milestone => milestone.name === selectedMilestone,
hidden: () => {
$selectBox.hide();
// display:block overrides the hide-collapse rule
@@ -137,7 +136,7 @@ export default class MilestoneSelect {
selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
}
$('a.is-active', $el).removeClass('is-active');
- $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
+ $(`[data-milestone-id="${_.escape(selectedMilestone)}"] > a`, $el).addClass('is-active');
},
vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: (clickEvent) => {
@@ -158,6 +157,7 @@ export default class MilestoneSelect {
const isMRIndex = (page === page && page === 'projects:merge_requests:index');
const isSelecting = (selected.name !== selectedMilestone);
selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
+
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
e.preventDefault();
return;
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index b0573510ff9..ac70ddb3ff4 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1190,12 +1190,12 @@ export default class Notes {
addForm = false;
let lineTypeSelector = '';
rowCssToAdd =
- '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content discussion-notes"></div></td></tr>';
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`;
rowCssToAdd =
- '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
+ '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content discussion-notes"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content discussion-notes"></div></td></tr>';
}
const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index e0f883a8e08..476b15aca4a 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -258,9 +258,7 @@ Please check your network connection and try again.`;
:key="note.id"
/>
</ul>
- <div
- :class="{ 'is-replying': isReplying }"
- class="discussion-reply-holder">
+ <div class="discussion-reply-holder">
<template v-if="!isReplying && canReply">
<div
class="btn-group-justified discussion-with-resolve-btn"
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index d149b307e7f..914f804fdd3 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
+ isGroupDecendent: true,
});
projectSelect();
});
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index a5cc1f34b63..1600faa3611 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -5,6 +5,7 @@ import { FILTERED_SEARCH } from '~/pages/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
+ isGroupDecendent: true,
});
projectSelect();
});
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue
index 9b136573135..d501c465a96 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue
@@ -17,6 +17,7 @@
* "text": "passed",
* "label": "passed",
* "group": "success",
+ * "tooltip": "passed",
* "details_path": "/root/ci-mock/builds/4256",
* "action": {
* "icon": "retry",
@@ -69,12 +70,12 @@
textBuilder.push(this.job.name);
}
- if (this.job.name && this.status.label) {
+ if (this.job.name && this.status.tooltip) {
textBuilder.push('-');
}
- if (this.status.label) {
- textBuilder.push(`${this.job.status.label}`);
+ if (this.status.tooltip) {
+ textBuilder.push(`${this.job.status.tooltip}`);
}
return textBuilder.join(' ');
@@ -100,6 +101,7 @@
:title="tooltipText"
:class="cssClassJobName"
data-container="body"
+ data-html="true"
class="js-pipeline-graph-job-link"
>
@@ -115,6 +117,7 @@
class="js-job-component-tooltip"
:title="tooltipText"
:class="cssClassJobName"
+ data-html="true"
data-container="body"
>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
new file mode 100644
index 00000000000..fb8ccea91c7
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue
@@ -0,0 +1,43 @@
+<script>
+import { viewerInformationForPath } from './lib/viewer_utils';
+import MarkdownViewer from './viewers/markdown_viewer.vue';
+
+export default {
+ props: {
+ content: {
+ type: String,
+ required: true,
+ },
+ path: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ viewer() {
+ const previewInfo = viewerInformationForPath(this.path);
+ switch (previewInfo.id) {
+ case 'markdown':
+ return MarkdownViewer;
+ default:
+ return null;
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="preview-container">
+ <component
+ :is="viewer"
+ :project-path="projectPath"
+ :content="content"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
new file mode 100644
index 00000000000..4f2e1e47dd1
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/lib/viewer_utils.js
@@ -0,0 +1,23 @@
+const viewers = {
+ markdown: {
+ id: 'markdown',
+ previewTitle: 'Preview Markdown',
+ },
+};
+
+const fileNameViewers = {};
+const fileExtensionViewers = {
+ md: 'markdown',
+ markdown: 'markdown',
+};
+
+export function viewerInformationForPath(path) {
+ if (!path) return null;
+ const name = path.split('/').pop();
+ const viewerName =
+ fileNameViewers[name] || fileExtensionViewers[name ? name.split('.').pop() : ''] || '';
+
+ return viewers[viewerName];
+}
+
+export default viewers;
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
new file mode 100644
index 00000000000..09e0094054d
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
@@ -0,0 +1,90 @@
+<script>
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import $ from 'jquery';
+import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
+
+const CancelToken = axios.CancelToken;
+let axiosSource;
+
+export default {
+ components: {
+ SkeletonLoadingContainer,
+ },
+ props: {
+ content: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ previewContent: null,
+ isLoading: false,
+ };
+ },
+ watch: {
+ content() {
+ this.previewContent = null;
+ },
+ },
+ created() {
+ axiosSource = CancelToken.source();
+ this.fetchMarkdownPreview();
+ },
+ updated() {
+ this.fetchMarkdownPreview();
+ },
+ destroyed() {
+ if (this.isLoading) axiosSource.cancel('Cancelling Preview');
+ },
+ methods: {
+ fetchMarkdownPreview() {
+ if (this.content && this.previewContent === null) {
+ this.isLoading = true;
+ const postBody = {
+ text: this.content,
+ };
+ const postOptions = {
+ cancelToken: axiosSource.token,
+ };
+
+ axios
+ .post(
+ `${gon.relative_url_root}/${this.projectPath}/preview_markdown`,
+ postBody,
+ postOptions,
+ )
+ .then(({ data }) => {
+ this.previewContent = data.body;
+ this.isLoading = false;
+
+ this.$nextTick(() => {
+ $(this.$refs['markdown-preview']).renderGFM();
+ });
+ })
+ .catch(() => {
+ this.previewContent = __('An error occurred while fetching markdown preview');
+ this.isLoading = false;
+ });
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ ref="markdown-preview"
+ class="md md-previewer">
+ <skeleton-loading-container v-if="isLoading" />
+ <div
+ v-else
+ v-html="previewContent">
+ </div>
+ </div>
+</template>
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 7e829826eba..f1a8a46dda4 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -24,6 +24,10 @@
color: $list-text-disabled-color;
}
+ &:not(.ui-sort-disabled):hover {
+ background: $row-hover;
+ }
+
&.unstyled {
&:hover {
background: none;
@@ -34,14 +38,15 @@
background-color: $list-warning-row-bg;
border-color: $list-warning-row-border;
color: $list-warning-row-color;
- }
- &.smoke { background-color: $gray-light; }
+ &:hover {
+ background: $list-warning-row-bg;
+ }
- &:not(.ui-sort-disabled):hover {
- background: $row-hover;
}
+ &.smoke { background-color: $gray-light; }
+
&:last-child {
border-bottom: 0;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 294c59f037f..9e1371648ed 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -289,6 +289,11 @@ body {
&:last-child {
margin-bottom: 0;
}
+
+ &.with-button {
+ line-height: 34px;
+ }
+
}
.page-title-empty {
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 98d460339cd..7a6352e45f1 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -391,7 +391,7 @@
}
&:hover {
- background-color: $row-hover;
+ background-color: $dropdown-item-hover-bg;
}
.icon-retry {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 7f037582ca0..679f783b1b6 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -813,6 +813,7 @@
}
.discussion-notes {
+ padding: 0 $gl-padding $gl-padding;
min-height: 35px;
&:first-child {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 4a528bc2bb1..8720f821ce9 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -173,11 +173,7 @@
}
.discussion-form {
- background-color: $white-light;
-}
-
-.discussion-form-container {
- padding: $gl-padding-top $gl-padding $gl-padding;
+ padding-top: $gl-padding-top;
}
.discussion-notes .disabled-comment {
@@ -237,12 +233,7 @@
.discussion-body,
.diff-file {
.discussion-reply-holder {
- background-color: $white-light;
- padding: 10px 16px;
-
- &.is-replying {
- padding-bottom: $gl-padding;
- }
+ padding-top: $gl-padding;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 81e98f358a8..9d9cbecc958 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -47,7 +47,7 @@ ul.notes {
}
.timeline-entry-inner {
- padding: $gl-padding $gl-btn-padding;
+ padding: $gl-padding 0;
border-bottom: 1px solid $white-normal;
}
@@ -94,12 +94,6 @@ ul.notes {
}
}
- &.note-discussion {
- .timeline-entry-inner {
- padding: $gl-padding 10px;
- }
- }
-
.editing-spinner {
display: none;
}
@@ -352,6 +346,8 @@ ul.notes {
}
.discussion-notes {
+ background-color: $white-light;
+
&:not(:first-child) {
border-top: 1px solid $white-normal;
margin-top: 20px;
@@ -363,10 +359,6 @@ ul.notes {
}
}
- .notes {
- background-color: $white-light;
- }
-
a code {
top: 0;
margin-right: 0;
@@ -647,8 +639,6 @@ ul.notes {
border-bottom: 1px solid $white-normal;
.timeline-entry-inner {
- padding-left: $gl-padding;
- padding-right: $gl-padding;
border-bottom: 0;
}
}
diff --git a/app/assets/stylesheets/pages/pages.scss b/app/assets/stylesheets/pages/pages.scss
new file mode 100644
index 00000000000..fb42dee66d2
--- /dev/null
+++ b/app/assets/stylesheets/pages/pages.scss
@@ -0,0 +1,60 @@
+.pages-domain-list {
+ &-item {
+ position: relative;
+ display: flex;
+ align-items: center;
+
+ .domain-status {
+ display: inline-flex;
+ left: $gl-padding;
+ position: absolute;
+ }
+
+ .domain-name {
+ flex-grow: 1;
+ }
+
+ }
+
+ &.has-verification-status > li {
+ padding-left: 3 * $gl-padding;
+ }
+
+}
+
+.status-badge {
+
+ display: inline-flex;
+ margin-bottom: $gl-padding-8;
+
+ // Most of the following settings "stolen" from btn-sm
+ // Border radius is overwritten for both
+ .label,
+ .btn {
+ padding: $gl-padding-4 $gl-padding-8;
+ font-size: $gl-font-size;
+ line-height: $gl-btn-line-height;
+ border-radius: 0;
+ display: flex;
+ align-items: center;
+ }
+
+ .btn svg {
+ top: auto;
+ }
+
+ :first-child {
+ border-bottom-left-radius: $border-radius-default;
+ border-top-left-radius: $border-radius-default;
+ }
+
+ :not(:first-child) {
+ border-left: 0;
+ }
+
+ :last-child {
+ border-bottom-right-radius: $border-radius-default;
+ border-top-right-radius: $border-radius-default;
+ }
+
+}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index ac745019319..b199f9876d3 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -210,13 +210,8 @@
}
.created-personal-access-token-container {
- #created-personal-access-token {
- width: 90%;
- display: inline;
- }
-
.btn-clipboard {
- margin-left: 5px;
+ border: 1px solid $border-color;
}
}
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index 1f6f7138e1f..8cc5c8fc877 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -308,14 +308,34 @@
height: 100%;
}
-.multi-file-editor-btn-group {
- padding: $gl-bar-padding $gl-padding;
- border-top: 1px solid $white-dark;
+.preview-container {
+ height: 100%;
+ overflow: auto;
+
+ .md-previewer {
+ padding: $gl-padding;
+ }
+}
+
+.ide-mode-tabs {
border-bottom: 1px solid $white-dark;
- background: $white-light;
+
+ .nav-links {
+ border-bottom: 0;
+
+ li a {
+ padding: $gl-padding-8 $gl-padding;
+ line-height: $gl-btn-line-height;
+ }
+ }
+}
+
+.ide-btn-group {
+ padding: $gl-padding-4 $gl-vert-padding;
}
.ide-status-bar {
+ border-top: 1px solid $white-dark;
padding: $gl-bar-padding $gl-padding;
background: $white-light;
display: flex;
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index 7bc16214010..8e86af43fee 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -4,8 +4,8 @@ class Projects::DiscussionsController < Projects::ApplicationController
before_action :check_merge_requests_available!
before_action :merge_request
- before_action :discussion
- before_action :authorize_resolve_discussion!
+ before_action :discussion, only: [:resolve, :unresolve]
+ before_action :authorize_resolve_discussion!, only: [:resolve, :unresolve]
def resolve
Discussions::ResolveService.new(project, current_user, merge_request: merge_request).execute(discussion)
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index dd5e66f60e3..07249fe3182 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -7,6 +7,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
attr_reader :authentication_result, :redirected_path
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
+ delegate :type, to: :authentication_result, allow_nil: true, prefix: :auth_result
alias_method :user, :actor
alias_method :authenticated_user, :actor
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 45910a9be44..1dcf837f78e 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -64,7 +64,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
@access ||= access_klass.new(access_actor, project,
'http', authentication_abilities: authentication_abilities,
namespace_path: params[:namespace_id], project_path: project_path,
- redirected_path: redirected_path)
+ redirected_path: redirected_path, auth_result_type: auth_result_type)
end
def access_actor
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 85e972d9731..dd12d30a085 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -2,7 +2,6 @@ class Projects::JobsController < Projects::ApplicationController
include SendFileUpload
before_action :build, except: [:index, :cancel_all]
-
before_action :authorize_read_build!,
only: [:index, :show, :status, :raw, :trace]
before_action :authorize_update_build!,
@@ -45,8 +44,11 @@ class Projects::JobsController < Projects::ApplicationController
end
def show
- @builds = @project.pipelines.find_by_sha(@build.sha).builds.order('id DESC')
- @builds = @builds.where("id not in (?)", @build.id)
+ @builds = @project.pipelines
+ .find_by_sha(@build.sha)
+ .builds
+ .order('id DESC')
+ .present(current_user: current_user)
@pipeline = @build.pipeline
respond_to do |format|
@@ -128,7 +130,7 @@ class Projects::JobsController < Projects::ApplicationController
if stream.file?
send_file stream.path, type: 'text/plain; charset=utf-8', disposition: 'inline'
else
- render_404
+ send_data stream.raw, type: 'text/plain; charset=utf-8', disposition: 'inline', filename: 'job.log'
end
end
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 516198b1b8a..91016f6494e 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -150,7 +150,8 @@ class Projects::LabelsController < Projects::ApplicationController
end
def find_labels
- @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
+ @available_labels ||=
+ LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: params[:include_ancestor_groups]).execute
end
def authorize_admin_labels!
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index c77f10ef1dd..ee4ed674110 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -41,7 +41,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
def existing_oids
@existing_oids ||= begin
- storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
+ project.all_lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid)
end
end
diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb
index 2515e4b9a17..ebde0df1f7b 100644
--- a/app/controllers/projects/lfs_storage_controller.rb
+++ b/app/controllers/projects/lfs_storage_controller.rb
@@ -31,7 +31,9 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
render plain: 'Unprocessable entity', status: 422
end
rescue ActiveRecord::RecordInvalid
- render_400
+ render_lfs_forbidden
+ rescue UploadedFile::InvalidPathError
+ render_lfs_forbidden
rescue ObjectStorage::RemoteStoreError
render_lfs_forbidden
end
@@ -66,10 +68,11 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
end
def create_file!(oid, size)
- LfsObject.new(oid: oid, size: size).tap do |object|
- object.file.store_workhorse_file!(params, :file)
- object.save!
- end
+ uploaded_file = UploadedFile.from_params(
+ params, :file, LfsObjectUploader.workhorse_local_upload_path)
+ return unless uploaded_file
+
+ LfsObject.create!(oid: oid, size: size, file: uploaded_file)
end
def link_to_project!(object)
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index 557671ab186..73c613b26f3 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -4,41 +4,4 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def show
redirect_to project_settings_ci_cd_path(@project, params: params)
end
-
- def update
- Projects::UpdateService.new(project, current_user, update_params).tap do |service|
- if service.execute
- flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
-
- run_autodevops_pipeline(service)
-
- redirect_to project_settings_ci_cd_path(@project)
- else
- render 'show'
- end
- end
- end
-
- private
-
- def run_autodevops_pipeline(service)
- return unless service.run_auto_devops_pipeline?
-
- if @project.empty_repo?
- flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
- return
- end
-
- CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false)
- flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe
- end
-
- def update_params
- params.require(:project).permit(
- :runners_token, :builds_enabled, :build_allow_git_fetch,
- :build_timeout_in_minutes, :build_coverage_regex, :public_builds,
- :auto_cancel_pending_pipelines, :ci_config_path,
- auto_devops_attributes: [:id, :domain, :enabled]
- )
- end
end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 2376f469213..48a09e1ddb8 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -25,7 +25,7 @@ class Projects::RefsController < Projects::ApplicationController
when "graphs_commits"
commits_project_graph_path(@project, @id)
when "badges"
- project_pipelines_settings_path(@project, ref: @id)
+ project_settings_ci_cd_path(@project, ref: @id)
else
project_commits_path(@project, @id)
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index 96125b549b7..d80ef8113aa 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -2,13 +2,24 @@ module Projects
module Settings
class CiCdController < Projects::ApplicationController
before_action :authorize_admin_pipeline!
+ before_action :define_variables
def show
- define_runners_variables
- define_secret_variables
- define_triggers_variables
- define_badges_variables
- define_auto_devops_variables
+ end
+
+ def update
+ Projects::UpdateService.new(project, current_user, update_params).tap do |service|
+ result = service.execute
+ if result[:status] == :success
+ flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
+
+ run_autodevops_pipeline(service)
+
+ redirect_to project_settings_ci_cd_path(@project)
+ else
+ render 'show'
+ end
+ end
end
def reset_cache
@@ -25,6 +36,35 @@ module Projects
private
+ def update_params
+ params.require(:project).permit(
+ :runners_token, :builds_enabled, :build_allow_git_fetch,
+ :build_timeout_human_readable, :build_coverage_regex, :public_builds,
+ :auto_cancel_pending_pipelines, :ci_config_path,
+ auto_devops_attributes: [:id, :domain, :enabled]
+ )
+ end
+
+ def run_autodevops_pipeline(service)
+ return unless service.run_auto_devops_pipeline?
+
+ if @project.empty_repo?
+ flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+ return
+ end
+
+ CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false)
+ flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe
+ end
+
+ def define_variables
+ define_runners_variables
+ define_secret_variables
+ define_triggers_variables
+ define_badges_variables
+ define_auto_devops_variables
+ end
+
def define_runners_variables
@project_runners = @project.runners.ordered
@assignable_runners = current_user.ci_authorized_runners
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ee197c75764..37f14230196 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -324,7 +324,7 @@ class ProjectsController < Projects::ApplicationController
:avatar,
:build_allow_git_fetch,
:build_coverage_regex,
- :build_timeout_in_minutes,
+ :build_timeout_human_readable,
:resolve_outdated_diff_discussions,
:container_registry_enabled,
:default_branch,
diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb
index 780c0fdb03e..afd1f824b32 100644
--- a/app/finders/labels_finder.rb
+++ b/app/finders/labels_finder.rb
@@ -28,9 +28,10 @@ class LabelsFinder < UnionFinder
if project
if project.group.present?
labels_table = Label.arel_table
+ group_ids = group_ids_for(project.group)
label_ids << Label.where(
- labels_table[:type].eq('GroupLabel').and(labels_table[:group_id].eq(project.group.id)).or(
+ labels_table[:type].eq('GroupLabel').and(labels_table[:group_id].in(group_ids)).or(
labels_table[:type].eq('ProjectLabel').and(labels_table[:project_id].eq(project.id))
)
)
@@ -38,11 +39,14 @@ class LabelsFinder < UnionFinder
label_ids << project.labels
end
end
- elsif only_group_labels?
- label_ids << Label.where(group_id: group_ids)
else
+ if group?
+ group = Group.find(params[:group_id])
+ label_ids << Label.where(group_id: group_ids_for(group))
+ end
+
label_ids << Label.where(group_id: projects.group_ids)
- label_ids << Label.where(project_id: projects.select(:id))
+ label_ids << Label.where(project_id: projects.select(:id)) unless only_group_labels?
end
label_ids
@@ -59,22 +63,33 @@ class LabelsFinder < UnionFinder
items.where(title: title)
end
- def group_ids
+ # Gets redacted array of group ids
+ # which can include the ancestors and descendants of the requested group.
+ def group_ids_for(group)
strong_memoize(:group_ids) do
- groups_user_can_read_labels(groups_to_include).map(&:id)
+ groups = groups_to_include(group)
+
+ groups_user_can_read_labels(groups).map(&:id)
end
end
- def groups_to_include
- group = Group.find(params[:group_id])
+ def groups_to_include(group)
groups = [group]
- groups += group.ancestors if params[:include_ancestor_groups].present?
- groups += group.descendants if params[:include_descendant_groups].present?
+ groups += group.ancestors if include_ancestor_groups?
+ groups += group.descendants if include_descendant_groups?
groups
end
+ def include_ancestor_groups?
+ params[:include_ancestor_groups]
+ end
+
+ def include_descendant_groups?
+ params[:include_descendant_groups]
+ end
+
def group?
params[:group_id].present?
end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 275e892b2e6..af878bcf9a0 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -53,10 +53,12 @@ module BoardsHelper
end
def board_list_data
+ include_descendant_groups = @group&.present?
+
{
toggle: "dropdown",
- list_labels_path: labels_filter_path(true),
- labels: labels_filter_path(true),
+ list_labels_path: labels_filter_path(true, include_ancestor_groups: true),
+ labels: labels_filter_path(true, include_descendant_groups: include_descendant_groups),
labels_endpoint: @labels_endpoint,
namespace_path: @namespace_path,
project_path: @project&.path,
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 87ff607dc3f..c4a6a1e4bb3 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -129,13 +129,17 @@ module LabelsHelper
end
end
- def labels_filter_path(only_group_labels = false)
+ def labels_filter_path(only_group_labels = false, include_ancestor_groups: true, include_descendant_groups: false)
project = @target_project || @project
+ options = {}
+ options[:include_ancestor_groups] = include_ancestor_groups if include_ancestor_groups
+ options[:include_descendant_groups] = include_descendant_groups if include_descendant_groups
+
if project
- project_labels_path(project, :json)
+ project_labels_path(project, :json, options)
elsif @group
- options = { only_group_labels: only_group_labels } if only_group_labels
+ options[:only_group_labels] = only_group_labels if only_group_labels
group_labels_path(@group, :json, options)
else
dashboard_labels_path(:json)
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index f435c80c656..f872990122e 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -1,4 +1,29 @@
module ServicesHelper
+ def service_event_description(event)
+ case event
+ when "push", "push_events"
+ "Event will be triggered by a push to the repository"
+ when "tag_push", "tag_push_events"
+ "Event will be triggered when a new tag is pushed to the repository"
+ when "note", "note_events"
+ "Event will be triggered when someone adds a comment"
+ when "confidential_note", "confidential_note_events"
+ "Event will be triggered when someone adds a comment on a confidential issue"
+ when "issue", "issue_events"
+ "Event will be triggered when an issue is created/updated/closed"
+ when "confidential_issue", "confidential_issues_events"
+ "Event will be triggered when a confidential issue is created/updated/closed"
+ when "merge_request", "merge_request_events"
+ "Event will be triggered when a merge request is created/updated/merged"
+ when "pipeline", "pipeline_events"
+ "Event will be triggered when a pipeline status changes"
+ when "wiki_page", "wiki_page_events"
+ "Event will be triggered when a wiki page is created/updated"
+ when "commit", "commit_events"
+ "Event will be triggered when a commit is created/updated"
+ end
+ end
+
def service_event_field_name(event)
event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
"#{event}_events"
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index b64be89c181..5e7c20ef51e 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -123,7 +123,7 @@ module TreeHelper
# returns the relative path of the first subdir that doesn't have only one directory descendant
def flatten_tree(root_path, tree)
- return tree.flat_path.sub(%r{\A#{root_path}/}, '') if tree.flat_path.present?
+ return tree.flat_path.sub(%r{\A#{Regexp.escape(root_path)}/}, '') if tree.flat_path.present?
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir?
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index 2a6406d63c7..fb66dd0b766 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -16,7 +16,7 @@ class Appearance < ActiveRecord::Base
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- CACHE_KEY = 'current_appearance'.freeze
+ CACHE_KEY = "current_appearance:#{Gitlab::VERSION}".freeze
after_commit :flush_redis_cache
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 18e96389199..4aa65bf4273 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -90,6 +90,7 @@ module Ci
before_save :ensure_token
before_destroy { unscoped_project }
+ before_create :ensure_metadata
after_create unless: :importing? do |build|
run_after_commit { BuildHooksWorker.perform_async(build.id) }
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index df57b4f65e3..fbb95fe16df 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -7,6 +7,7 @@ module Ci
belongs_to :project
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
+ before_save :update_file_store
before_save :set_size, if: :file_changed?
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
@@ -21,6 +22,10 @@ module Ci
trace: 3
}
+ def update_file_store
+ self.file_store = file.object_store
+ end
+
def self.artifacts_size_for(project)
self.where(project: project).sum(:size)
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index b64462fb768..3f7f36e83c0 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -32,7 +32,8 @@ class Commit
COMMIT_SHA_PATTERN = /\h{#{MIN_SHA_LENGTH},40}/.freeze
def banzai_render_context(field)
- context = { pipeline: :single_line, project: self.project }
+ pipeline = field == :description ? :commit_description : :single_line
+ context = { pipeline: pipeline, project: self.project }
context[:author] = self.author if self.author
context
diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb
index fa1eafb1d7a..593a9b3d71d 100644
--- a/app/models/concerns/chronic_duration_attribute.rb
+++ b/app/models/concerns/chronic_duration_attribute.rb
@@ -8,14 +8,14 @@ module ChronicDurationAttribute
end
end
- def chronic_duration_attr_writer(virtual_attribute, source_attribute)
+ def chronic_duration_attr_writer(virtual_attribute, source_attribute, parameters = {})
chronic_duration_attr_reader(virtual_attribute, source_attribute)
define_method("#{virtual_attribute}=") do |value|
- chronic_duration_attributes[virtual_attribute] = value.presence || ''
+ chronic_duration_attributes[virtual_attribute] = value.presence || parameters[:default].presence.to_s
begin
- new_value = ChronicDuration.parse(value).to_i if value.present?
+ new_value = value.present? ? ChronicDuration.parse(value).to_i : parameters[:default].presence
assign_attributes(source_attribute => new_value)
rescue ChronicDuration::DurationParseError
# ignore error as it will be caught by validation
diff --git a/app/models/concerns/presentable.rb b/app/models/concerns/presentable.rb
index 7b33b837004..bc4fbd19a02 100644
--- a/app/models/concerns/presentable.rb
+++ b/app/models/concerns/presentable.rb
@@ -1,4 +1,12 @@
module Presentable
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def present(attributes)
+ all.map { |klass_object| klass_object.present(attributes) }
+ end
+ end
+
def present(**attributes)
Gitlab::View::Presenter::Factory
.new(self, attributes)
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index b6dd39b860b..ec072882cc9 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -7,6 +7,7 @@ class ProjectHook < WebHook
:issue_hooks,
:confidential_issue_hooks,
:note_hooks,
+ :confidential_note_hooks,
:merge_request_hooks,
:job_hooks,
:pipeline_hooks,
diff --git a/app/models/note.rb b/app/models/note.rb
index 0f5fb529a87..e426f84832b 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -268,6 +268,10 @@ class Note < ActiveRecord::Base
self.special_role = Note::SpecialRole::FIRST_TIME_CONTRIBUTOR
end
+ def confidential?
+ noteable.try(:confidential?)
+ end
+
def editable?
!system?
end
@@ -313,6 +317,10 @@ class Note < ActiveRecord::Base
!system? && !for_snippet?
end
+ def can_create_notification?
+ true
+ end
+
def discussion_class(noteable = nil)
# When commit notes are rendered on an MR's Discussion page, they are
# displayed in one discussion instead of individually.
diff --git a/app/models/project.rb b/app/models/project.rb
index 714a15ade9c..1b29cbf28d2 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -21,6 +21,7 @@ class Project < ActiveRecord::Base
include Gitlab::SQL::Pattern
include DeploymentPlatform
include ::Gitlab::Utils::StrongMemoize
+ include ChronicDurationAttribute
extend Gitlab::ConfigHelper
@@ -325,6 +326,12 @@ class Project < ActiveRecord::Base
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
+ chronic_duration_attr :build_timeout_human_readable, :build_timeout, default: 3600
+
+ validates :build_timeout, allow_nil: true,
+ numericality: { greater_than_or_equal_to: 600,
+ message: 'needs to be at least 10 minutes' }
+
# Returns a collection of projects that is either public or visible to the
# logged in user.
def self.public_or_visible_to_user(user = nil)
@@ -630,7 +637,7 @@ class Project < ActiveRecord::Base
end
def create_or_update_import_data(data: nil, credentials: nil)
- return unless import_url.present? && valid_import_url?
+ return if data.nil? && credentials.nil?
project_import_data = import_data || build_import_data
if data
@@ -1066,6 +1073,16 @@ class Project < ActiveRecord::Base
end
end
+ # This will return all `lfs_objects` that are accessible to the project.
+ # So this might be `self.lfs_objects` if the project is not part of a fork
+ # network, or it is the base of the fork network.
+ #
+ # TODO: refactor this to get the correct lfs objects when implementing
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
+ def all_lfs_objects
+ lfs_storage_project.lfs_objects
+ end
+
def personal?
!group
end
@@ -1299,14 +1316,6 @@ class Project < ActiveRecord::Base
self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
end
- def build_timeout_in_minutes
- build_timeout / 60
- end
-
- def build_timeout_in_minutes=(value)
- self.build_timeout = value.to_i * 60
- end
-
def open_issues_count
Projects::OpenIssuesCountService.new(self).count
end
@@ -1478,6 +1487,7 @@ class Project < ActiveRecord::Base
remove_import_jid
update_project_counter_caches
after_create_default_branch
+ refresh_markdown_cache!
end
def update_project_counter_caches
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index dab0ea1a681..7591ab4f478 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -21,8 +21,16 @@ class ChatNotificationService < Service
end
end
+ def confidential_issue_channel
+ properties['confidential_issue_channel'].presence || properties['issue_channel']
+ end
+
+ def confidential_note_channel
+ properties['confidential_note_channel'].presence || properties['note_channel']
+ end
+
def self.supported_events
- %w[push issue confidential_issue merge_request note tag_push
+ %w[push issue confidential_issue merge_request note confidential_note tag_push
pipeline wiki_page]
end
@@ -55,7 +63,9 @@ class ChatNotificationService < Service
return false unless message
- channel_name = get_channel_field(object_kind).presence || channel
+ event_type = data[:event_type] || object_kind
+
+ channel_name = get_channel_field(event_type).presence || channel
opts = {}
opts[:channel] = channel_name if channel_name
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index f31c3f02af2..dce878e485f 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -46,7 +46,7 @@ class HipchatService < Service
end
def self.supported_events
- %w(push issue confidential_issue merge_request note tag_push pipeline)
+ %w(push issue confidential_issue merge_request note confidential_note tag_push pipeline)
end
def execute(data)
diff --git a/app/models/service.rb b/app/models/service.rb
index 7424cef0fc0..e9b6f005aec 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -14,6 +14,7 @@ class Service < ActiveRecord::Base
default_value_for :merge_requests_events, true
default_value_for :tag_push_events, true
default_value_for :note_events, true
+ default_value_for :confidential_note_events, true
default_value_for :job_events, true
default_value_for :pipeline_events, true
default_value_for :wiki_page_events, true
@@ -42,6 +43,7 @@ class Service < ActiveRecord::Base
scope :confidential_issue_hooks, -> { where(confidential_issues_events: true, active: true) }
scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) }
scope :note_hooks, -> { where(note_events: true, active: true) }
+ scope :confidential_note_hooks, -> { where(confidential_note_events: true, active: true) }
scope :job_hooks, -> { where(job_events: true, active: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
@@ -168,8 +170,10 @@ class Service < ActiveRecord::Base
def self.prop_accessor(*args)
args.each do |arg|
class_eval %{
- def #{arg}
- properties['#{arg}']
+ unless method_defined?(arg)
+ def #{arg}
+ properties['#{arg}']
+ end
end
def #{arg}=(value)
diff --git a/app/models/user.rb b/app/models/user.rb
index ba51595e6a3..ce56b39b1c8 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -164,12 +164,15 @@ class User < ActiveRecord::Base
before_validation :sanitize_attrs
before_validation :set_notification_email, if: :email_changed?
+ before_save :set_notification_email, if: :email_changed? # in case validation is skipped
before_validation :set_public_email, if: :public_email_changed?
+ before_save :set_public_email, if: :public_email_changed? # in case validation is skipped
before_save :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? }
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
before_validation :ensure_namespace_correct
+ before_save :ensure_namespace_correct # in case validation is skipped
after_validation :set_username_errors
after_update :username_changed_hook, if: :username_changed?
after_destroy :post_destroy_hook
@@ -408,7 +411,6 @@ class User < ActiveRecord::Base
unique_internal(where(ghost: true), 'ghost', email) do |u|
u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.'
u.name = 'Ghost User'
- u.notification_email = email
end
end
end
@@ -1044,11 +1046,6 @@ class User < ActiveRecord::Base
end
end
- def update_cache_counts
- assigned_open_merge_requests_count(force: true)
- assigned_open_issues_count(force: true)
- end
-
def update_todos_count_cache
todos_done_count(force: true)
todos_pending_count(force: true)
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index 3f6d7d04667..e86d1c8f98e 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -2,20 +2,6 @@ class IssuablePolicy < BasePolicy
delegate { @subject.project }
condition(:locked, scope: :subject, score: 0) { @subject.discussion_locked? }
-
- # We aren't checking `:read_issue` or `:read_merge_request` in this case
- # because it could be possible for a user to see an issuable-iid
- # (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be allowed
- # to read the actual issue after a more expensive `:read_issue` check.
- #
- # `:read_issue` & `:read_issue_iid` could diverge in gitlab-ee.
- condition(:visible_to_user, score: 4) do
- Project.where(id: @subject.project)
- .public_or_visible_to_user(@user)
- .with_feature_available_for_user(@subject, @user)
- .any?
- end
-
condition(:is_project_member) { @user && @subject.project && @subject.project.team.member?(@user) }
desc "User is the assignee or author"
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index ed499511999..263c6e3039c 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -17,6 +17,4 @@ class IssuePolicy < IssuablePolicy
prevent :update_issue
prevent :admin_issue
end
-
- rule { can?(:read_issue) | visible_to_user }.enable :read_issue_iid
end
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index e003376d219..c3fe857f8a2 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -1,3 +1,2 @@
class MergeRequestPolicy < IssuablePolicy
- rule { can?(:read_merge_request) | visible_to_user }.enable :read_merge_request_iid
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 57ab0c23dcd..b1ed034cd00 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -66,6 +66,22 @@ class ProjectPolicy < BasePolicy
project.merge_requests_allowing_push_to_user(user).any?
end
+ # We aren't checking `:read_issue` or `:read_merge_request` in this case
+ # because it could be possible for a user to see an issuable-iid
+ # (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
+ # allowed to read the actual issue after a more expensive `:read_issue`
+ # check. These checks are intended to be used alongside
+ # `:read_project_for_iids`.
+ #
+ # `:read_issue` & `:read_issue_iid` could diverge in gitlab-ee.
+ condition(:issues_visible_to_user, score: 4) do
+ @subject.feature_available?(:issues, @user)
+ end
+
+ condition(:merge_requests_visible_to_user, score: 4) do
+ @subject.feature_available?(:merge_requests, @user)
+ end
+
features = %w[
merge_requests
issues
@@ -81,6 +97,10 @@ class ProjectPolicy < BasePolicy
condition(:"#{f}_disabled", score: 32) { !feature_available?(f.to_sym) }
end
+ # `:read_project` may be prevented in EE, but `:read_project_for_iids` should
+ # not.
+ rule { guest | admin }.enable :read_project_for_iids
+
rule { guest }.enable :guest_access
rule { reporter }.enable :reporter_access
rule { developer }.enable :developer_access
@@ -150,6 +170,7 @@ class ProjectPolicy < BasePolicy
# where we enable or prevent it based on other coditions.
rule { (~anonymous & public_project) | internal_access }.policy do
enable :public_user_access
+ enable :read_project_for_iids
end
rule { can?(:public_user_access) }.policy do
@@ -255,7 +276,11 @@ class ProjectPolicy < BasePolicy
end
rule { anonymous & ~public_project }.prevent_all
- rule { public_project }.enable(:public_access)
+
+ rule { public_project }.policy do
+ enable :public_access
+ enable :read_project_for_iids
+ end
rule { can?(:public_access) }.policy do
enable :read_project
@@ -305,6 +330,14 @@ class ProjectPolicy < BasePolicy
enable :update_pipeline
end
+ rule do
+ (can?(:read_project_for_iids) & issues_visible_to_user) | can?(:read_issue)
+ end.enable :read_issue_iid
+
+ rule do
+ (can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request)
+ end.enable :read_merge_request_iid
+
private
def team_member?
diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb
index 255475e1fe6..9afebda19be 100644
--- a/app/presenters/ci/build_presenter.rb
+++ b/app/presenters/ci/build_presenter.rb
@@ -15,6 +15,8 @@ module Ci
def status_title
if auto_canceled?
"Job is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
+ else
+ tooltip_for_badge
end
end
@@ -28,5 +30,19 @@ module Ci
trigger_request.user_variables
end
end
+
+ def tooltip_message
+ "#{subject.name} - #{detailed_status.status_tooltip}"
+ end
+
+ private
+
+ def tooltip_for_badge
+ detailed_status.badge_tooltip.capitalize
+ end
+
+ def detailed_status
+ @detailed_status ||= subject.detailed_status(user)
+ end
end
end
diff --git a/app/serializers/build_metadata_entity.rb b/app/serializers/build_metadata_entity.rb
index 39f429aa6c3..f16f3badffa 100644
--- a/app/serializers/build_metadata_entity.rb
+++ b/app/serializers/build_metadata_entity.rb
@@ -1,8 +1,5 @@
class BuildMetadataEntity < Grape::Entity
- expose :timeout_human_readable do |metadata|
- metadata.timeout_human_readable unless metadata.timeout.nil?
- end
-
+ expose :timeout_human_readable
expose :timeout_source do |metadata|
metadata.present.timeout_source
end
diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb
index a7c2e21e92b..8e8bda2f9df 100644
--- a/app/serializers/status_entity.rb
+++ b/app/serializers/status_entity.rb
@@ -2,7 +2,7 @@ class StatusEntity < Grape::Entity
include RequestAwareEntity
expose :icon, :text, :label, :group
-
+ expose :status_tooltip, as: :tooltip
expose :has_details?, as: :has_details
expose :details_path
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 15fed7d17c1..3ceab209f3f 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -42,7 +42,10 @@ module Boards
)
end
- attrs[:move_between_ids] = move_between_ids if move_between_ids
+ if move_between_ids
+ attrs[:move_between_ids] = move_between_ids
+ attrs[:board_group_id] = board.group&.id
+ end
attrs
end
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index bebc90c7a8d..02f1c709374 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -12,11 +12,15 @@ module Boards
private
def available_labels_for(board)
+ options = { include_ancestor_groups: true }
+
if board.group_board?
- parent.labels
+ options.merge!(group_id: parent.id, only_group_labels: true)
else
- LabelsFinder.new(current_user, project_id: parent.id).execute
+ options[:project_id] = parent.id
end
+
+ LabelsFinder.new(current_user, options).execute
end
def next_position(board)
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index e09b445636f..d46dcff34a1 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -4,6 +4,9 @@ module Ci
class RegisterJobService
attr_reader :runner
+ JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30].freeze
+ JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
+
Result = Struct.new(:build, :valid?)
def initialize(runner)
@@ -104,10 +107,22 @@ module Ci
end
def register_success(job)
- job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
+ labels = { shared_runner: runner.shared?,
+ jobs_running_for_project: jobs_running_for_project(job) }
+
+ job_queue_duration_seconds.observe(labels, Time.now - job.queued_at)
attempt_counter.increment
end
+ def jobs_running_for_project(job)
+ return '+Inf' unless runner.shared?
+
+ # excluding currently started job
+ running_jobs_count = job.project.builds.running.where(runner: Ci::Runner.shared)
+ .limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1
+ running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+"
+ end
+
def failed_attempt_counter
@failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job")
end
@@ -117,7 +132,7 @@ module Ci
end
def job_queue_duration_seconds
- @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time')
+ @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time', {}, JOB_QUEUE_DURATION_SECONDS_BUCKETS)
end
end
end
diff --git a/app/services/issuable/destroy_service.rb b/app/services/issuable/destroy_service.rb
index 7197a426a72..0b1a33518c6 100644
--- a/app/services/issuable/destroy_service.rb
+++ b/app/services/issuable/destroy_service.rb
@@ -4,6 +4,7 @@ module Issuable
TodoService.new.destroy_target(issuable) do |issuable|
if issuable.destroy
issuable.update_project_counter_caches
+ issuable.assignees.each(&:invalidate_cache_counts)
end
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 02fb48108fb..91ec702fbc6 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -106,7 +106,7 @@ class IssuableBaseService < BaseService
end
def available_labels
- @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute
+ @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id, include_ancestor_groups: true).execute
end
def handle_quick_actions_on_create(issuable)
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index d7aa7e2347e..4161932ad2a 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -55,9 +55,10 @@ module Issues
return unless params[:move_between_ids]
after_id, before_id = params.delete(:move_between_ids)
+ board_group_id = params.delete(:board_group_id)
- issue_before = get_issue_if_allowed(issue.project, before_id) if before_id
- issue_after = get_issue_if_allowed(issue.project, after_id) if after_id
+ issue_before = get_issue_if_allowed(before_id, board_group_id)
+ issue_after = get_issue_if_allowed(after_id, board_group_id)
issue.move_between(issue_before, issue_after)
end
@@ -84,8 +85,16 @@ module Issues
private
- def get_issue_if_allowed(project, id)
- issue = project.issues.find(id)
+ def get_issue_if_allowed(id, board_group_id = nil)
+ return unless id
+
+ issue =
+ if board_group_id
+ IssuesFinder.new(current_user, group_id: board_group_id).find_by(id: id)
+ else
+ project.issues.find(id)
+ end
+
issue if can?(current_user, :update_issue, issue)
end
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index ad3dcc5010b..199b8028dbc 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -11,7 +11,7 @@ module Notes
unless @note.system?
EventCreateService.new.leave_note(@note, @note.author)
- return unless @note.for_project_noteable?
+ return if @note.for_personal_snippet?
@note.create_cross_references!
execute_note_hooks
@@ -23,9 +23,13 @@ module Notes
end
def execute_note_hooks
+ return unless @note.project
+
note_data = hook_data
- @note.project.execute_hooks(note_data, :note_hooks)
- @note.project.execute_services(note_data, :note_hooks)
+ hooks_scope = @note.confidential? ? :confidential_note_hooks : :note_hooks
+
+ @note.project.execute_hooks(note_data, hooks_scope)
+ @note.project.execute_services(note_data, hooks_scope)
end
end
end
diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb
index e61ecb696d0..346971138b1 100644
--- a/app/services/projects/autocomplete_service.rb
+++ b/app/services/projects/autocomplete_service.rb
@@ -21,7 +21,8 @@ module Projects
end
def labels(target = nil)
- labels = LabelsFinder.new(current_user, project_id: project.id).execute.select([:color, :title])
+ labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true)
+ .execute.select([:color, :title])
return labels unless target&.respond_to?(:labels)
diff --git a/app/services/projects/gitlab_projects_import_service.rb b/app/services/projects/gitlab_projects_import_service.rb
index a68ecb4abe1..fb4afb85588 100644
--- a/app/services/projects/gitlab_projects_import_service.rb
+++ b/app/services/projects/gitlab_projects_import_service.rb
@@ -5,8 +5,8 @@ module Projects
class GitlabProjectsImportService
attr_reader :current_user, :params
- def initialize(user, params)
- @current_user, @params = user, params.dup
+ def initialize(user, import_params, override_params = nil)
+ @current_user, @params, @override_params = user, import_params.dup, override_params
end
def execute
@@ -17,6 +17,7 @@ module Projects
params[:import_type] = 'gitlab_project'
params[:import_source] = import_upload_path
+ params[:import_data] = { data: { override_params: @override_params } } if @override_params
::Projects::CreateService.new(current_user, params).execute
end
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index 402cddd3ec1..7bf0b90b491 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -28,7 +28,7 @@ module Projects
end
def save_services
- [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
+ [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver, lfs_saver].all?(&:save)
end
def version_saver
@@ -55,6 +55,10 @@ module Projects
Gitlab::ImportExport::WikiRepoSaver.new(project: project, shared: @shared)
end
+ def lfs_saver
+ Gitlab::ImportExport::LfsSaver.new(project: project, shared: @shared)
+ end
+
def cleanup_and_notify_error
Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}")
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index cba49faac31..6cc51b6ee1b 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -200,7 +200,7 @@ module QuickActions
end
params '~label1 ~"label 2"'
condition do
- available_labels = LabelsFinder.new(current_user, project_id: project.id).execute
+ available_labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true).execute
current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
available_labels.any?
@@ -562,7 +562,7 @@ module QuickActions
def find_labels(labels_param)
extract_references(labels_param, :label) |
- LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute
+ LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split, include_ancestor_groups: true).execute
end
def find_label_references(labels_param)
@@ -593,6 +593,7 @@ module QuickActions
def extract_references(arg, type)
ext = Gitlab::ReferenceExtractor.new(project, current_user)
+
ext.analyze(arg, author: current_user)
ext.references(type)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 2253d638e93..00bf5434b7f 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -429,7 +429,7 @@ module SystemNoteService
def cross_reference(noteable, mentioner, author)
return if cross_reference_disallowed?(noteable, mentioner)
- gfm_reference = mentioner.gfm_reference(noteable.project)
+ gfm_reference = mentioner.gfm_reference(noteable.project || noteable.group)
body = cross_reference_note_content(gfm_reference)
if noteable.is_a?(ExternalIssue)
@@ -582,7 +582,7 @@ module SystemNoteService
text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}"
notes.where('(note LIKE ? OR note LIKE ?)', text, text.capitalize)
else
- gfm_reference = mentioner.gfm_reference(noteable.project)
+ gfm_reference = mentioner.gfm_reference(noteable.project || noteable.group)
text = cross_reference_note_content(gfm_reference)
notes.where(note: [text, text.capitalize])
end
diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb
index ef0f8acefd6..dd86753479d 100644
--- a/app/uploaders/job_artifact_uploader.rb
+++ b/app/uploaders/job_artifact_uploader.rb
@@ -2,6 +2,8 @@ class JobArtifactUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
+ ObjectNotReadyError = Class.new(StandardError)
+
storage_options Gitlab.config.artifacts
def size
@@ -25,6 +27,8 @@ class JobArtifactUploader < GitlabUploader
private
def dynamic_segment
+ raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id
+
creation_date = model.created_at.utc.strftime('%Y_%m_%d')
File.join(disk_hash[0..1], disk_hash[2..3], disk_hash,
diff --git a/app/uploaders/legacy_artifact_uploader.rb b/app/uploaders/legacy_artifact_uploader.rb
index b726b053493..efb7893d153 100644
--- a/app/uploaders/legacy_artifact_uploader.rb
+++ b/app/uploaders/legacy_artifact_uploader.rb
@@ -2,6 +2,8 @@ class LegacyArtifactUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
+ ObjectNotReadyError = Class.new(StandardError)
+
storage_options Gitlab.config.artifacts
def store_dir
@@ -11,6 +13,8 @@ class LegacyArtifactUploader < GitlabUploader
private
def dynamic_segment
+ raise ObjectNotReadyError, 'Build is not ready' unless model.id
+
File.join(model.created_at.utc.strftime('%Y_%m'), model.project_id.to_s, model.id.to_s)
end
end
diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb
index 4028b052768..bd258e04d3f 100644
--- a/app/uploaders/object_storage.rb
+++ b/app/uploaders/object_storage.rb
@@ -128,7 +128,7 @@ module ObjectStorage
end
def direct_upload_enabled?
- object_store_options.direct_upload
+ object_store_options&.direct_upload
end
def background_upload_enabled?
@@ -156,11 +156,10 @@ module ObjectStorage
end
def workhorse_authorize
- if options = workhorse_remote_upload_options
- { RemoteObject: options }
- else
- { TempPath: workhorse_local_upload_path }
- end
+ {
+ RemoteObject: workhorse_remote_upload_options,
+ TempPath: workhorse_local_upload_path
+ }.compact
end
def workhorse_local_upload_path
@@ -184,6 +183,14 @@ module ObjectStorage
StoreURL: connection.put_object_url(remote_store_path, upload_path, expire_at, options)
}
end
+
+ def default_object_store
+ if self.object_store_enabled? && self.direct_upload_enabled?
+ Store::REMOTE
+ else
+ Store::LOCAL
+ end
+ end
end
# allow to configure and overwrite the filename
@@ -204,12 +211,12 @@ module ObjectStorage
end
def object_store
- @object_store ||= model.try(store_serialization_column) || Store::LOCAL
+ @object_store ||= model.try(store_serialization_column) || self.class.default_object_store
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def object_store=(value)
- @object_store = value || Store::LOCAL
+ @object_store = value || self.class.default_object_store
@storage = storage_for(object_store)
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
@@ -285,16 +292,14 @@ module ObjectStorage
}
end
- def store_workhorse_file!(params, identifier)
- filename = params["#{identifier}.name"]
-
- if remote_object_id = params["#{identifier}.remote_id"]
- store_remote_file!(remote_object_id, filename)
- elsif local_path = params["#{identifier}.path"]
- store_local_file!(local_path, filename)
- else
- raise RemoteStoreError, 'Bad file'
+ def cache!(new_file = sanitized_file)
+ # We intercept ::UploadedFile which might be stored on remote storage
+ # We use that for "accelerated" uploads, where we store result on remote storage
+ if new_file.is_a?(::UploadedFile) && new_file.remote_id
+ return cache_remote_file!(new_file.remote_id, new_file.original_filename)
end
+
+ super
end
private
@@ -305,36 +310,29 @@ module ObjectStorage
self.file_storage?
end
- def store_remote_file!(remote_object_id, filename)
- raise RemoteStoreError, 'Missing filename' unless filename
-
+ def cache_remote_file!(remote_object_id, original_filename)
file_path = File.join(TMP_UPLOAD_PATH, remote_object_id)
file_path = Pathname.new(file_path).cleanpath.to_s
raise RemoteStoreError, 'Bad file path' unless file_path.start_with?(TMP_UPLOAD_PATH + '/')
- self.object_store = Store::REMOTE
-
# TODO:
# This should be changed to make use of `tmp/cache` mechanism
# instead of using custom upload directory,
# using tmp/cache makes this implementation way easier than it is today
- CarrierWave::Storage::Fog::File.new(self, storage, file_path).tap do |file|
+ CarrierWave::Storage::Fog::File.new(self, storage_for(Store::REMOTE), file_path).tap do |file|
raise RemoteStoreError, 'Missing file' unless file.exists?
- self.filename = filename
- self.file = storage.store!(file)
- end
- end
-
- def store_local_file!(local_path, filename)
- raise RemoteStoreError, 'Missing filename' unless filename
+ # Remote stored file, we force to store on remote storage
+ self.object_store = Store::REMOTE
- root_path = File.realpath(self.class.workhorse_local_upload_path)
- file_path = File.realpath(local_path)
- raise RemoteStoreError, 'Bad file path' unless file_path.start_with?(root_path)
-
- self.object_store = Store::LOCAL
- self.store!(UploadedFile.new(file_path, filename))
+ # TODO:
+ # We store file internally and force it to be considered as `cached`
+ # This makes CarrierWave to store file in permament location (copy/delete)
+ # once this object is saved, but not sooner
+ @cache_id = "force-to-use-cache" # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @file = file # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @filename = original_filename # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
end
# this is a hack around CarrierWave. The #migrate method needs to be
diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml
new file mode 100644
index 00000000000..6c89f1c4e98
--- /dev/null
+++ b/app/views/admin/application_settings/_email.html.haml
@@ -0,0 +1,26 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :email_author_in_body do
+ = f.check_box :email_author_in_body
+ Include author name in notification email body
+ .help-block
+ Some email servers do not support overriding the email sender name.
+ Enable this option to include the name of the author of the issue,
+ merge request or comment in the email body instead.
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :html_emails_enabled do
+ = f.check_box :html_emails_enabled
+ Enable HTML emails
+ .help-block
+ By default GitLab sends emails in HTML and plain text formats so mail
+ clients can choose what format to use. Disable this option if you only
+ want to send emails in plain text format.
+
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
deleted file mode 100644
index 9ab2c2892b2..00000000000
--- a/app/views/admin/application_settings/_form.html.haml
+++ /dev/null
@@ -1,173 +0,0 @@
-= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
- = form_errors(@application_setting)
-
- - if Gitlab.config.registry.enabled
- %fieldset
- %legend Container Registry
- .form-group
- = f.label :container_registry_token_expire_delay, 'Authorization token duration (minutes)', class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :container_registry_token_expire_delay, class: 'form-control'
-
- - if koding_enabled?
- %fieldset
- %legend Koding
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :koding_enabled do
- = f.check_box :koding_enabled
- Enable Koding
- .help-block
- Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again.
- .form-group
- = f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2'
- .col-sm-10
- = f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
- .help-block
- Koding has integration enabled out of the box for the
- %strong gitlab
- team, and you need to provide that team's URL here. Learn more in the
- = succeed "." do
- = link_to "Koding administration documentation", help_page_path("administration/integration/koding")
-
- %fieldset
- %legend PlantUML
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :plantuml_enabled do
- = f.check_box :plantuml_enabled
- Enable PlantUML
- .form-group
- = f.label :plantuml_url, 'PlantUML URL', class: 'control-label col-sm-2'
- .col-sm-10
- = f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080'
- .help-block
- Allow rendering of
- = link_to "PlantUML", "http://plantuml.com"
- diagrams in Asciidoc documents using an external PlantUML service.
-
- %fieldset
- %legend#usage-statistics Usage statistics
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :version_check_enabled do
- = f.check_box :version_check_enabled
- Enable version check
- .help-block
- GitLab will inform you if a new version is available.
- = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "version-check")
- about what information is shared with GitLab Inc.
- .form-group
- .col-sm-offset-2.col-sm-10
- - can_be_configured = @application_setting.usage_ping_can_be_configured?
- .checkbox
- = f.label :usage_ping_enabled do
- = f.check_box :usage_ping_enabled, disabled: !can_be_configured
- Enable usage ping
- .help-block
- - if can_be_configured
- To help improve GitLab and its user experience, GitLab will
- periodically collect usage information.
- = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-ping")
- about what information is shared with GitLab Inc. Visit
- = link_to 'Cohorts', admin_cohorts_path(anchor: 'usage-ping')
- to see the JSON payload sent.
- - else
- The usage ping is disabled, and cannot be configured through this
- form. For more information, see the documentation on
- = succeed '.' do
- = link_to 'deactivating the usage ping', help_page_path('user/admin_area/settings/usage_statistics', anchor: 'deactivate-the-usage-ping')
-
- %fieldset
- %legend Email
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :email_author_in_body do
- = f.check_box :email_author_in_body
- Include author name in notification email body
- .help-block
- Some email servers do not support overriding the email sender name.
- Enable this option to include the name of the author of the issue,
- merge request or comment in the email body instead.
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :html_emails_enabled do
- = f.check_box :html_emails_enabled
- Enable HTML emails
- .help-block
- By default GitLab sends emails in HTML and plain text formats so mail
- clients can choose what format to use. Disable this option if you only
- want to send emails in plain text format.
-
- %fieldset
- %legend Gitaly Timeouts
- .form-group
- = f.label :gitaly_timeout_default, 'Default Timeout Period', class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :gitaly_timeout_default, class: 'form-control'
- .help-block
- Timeout for Gitaly calls from the GitLab application (in seconds). This timeout is not enforced
- for git fetch/push operations or Sidekiq jobs.
- .form-group
- = f.label :gitaly_timeout_fast, 'Fast Timeout Period', class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :gitaly_timeout_fast, class: 'form-control'
- .help-block
- Fast operation timeout (in seconds). Some Gitaly operations are expected to be fast.
- If they exceed this threshold, there may be a problem with a storage shard and 'failing fast'
- can help maintain the stability of the GitLab instance.
- .form-group
- = f.label :gitaly_timeout_medium, 'Medium Timeout Period', class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :gitaly_timeout_medium, class: 'form-control'
- .help-block
- Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout.
-
- %fieldset
- %legend Web terminal
- .form-group
- = f.label :terminal_max_session_time, 'Max session time', class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :terminal_max_session_time, class: 'form-control'
- .help-block
- Maximum time for web terminal websocket connection (in seconds).
- 0 for unlimited.
-
- %fieldset
- %legend Real-time features
- .form-group
- = f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'control-label col-sm-2'
- .col-sm-10
- = f.text_field :polling_interval_multiplier, class: 'form-control'
- .help-block
- Change this value to influence how frequently the GitLab UI polls for updates.
- If you set the value to 2 all polling intervals are multiplied
- by 2, which means that polling happens half as frequently.
- The multiplier can also have a decimal value.
- The default value (1) is a reasonable choice for the majority of GitLab
- installations. Set to 0 to completely disable polling.
- = link_to icon('question-circle'), help_page_path('administration/polling')
-
- %fieldset
- %legend Performance optimization
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :authorized_keys_enabled do
- = f.check_box :authorized_keys_enabled
- Write to "authorized_keys" file
- .help-block
- By default, we write to the "authorized_keys" file to support Git
- over SSH without additional configuration. GitLab can be optimized
- to authenticate SSH keys via the database file. Only uncheck this
- if you have configured your OpenSSH server to use the
- AuthorizedKeysCommand. Click on the help icon for more details.
- = link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
-
- .form-actions
- = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/admin/application_settings/_gitaly.html.haml b/app/views/admin/application_settings/_gitaly.html.haml
new file mode 100644
index 00000000000..4acc5b3a0c5
--- /dev/null
+++ b/app/views/admin/application_settings/_gitaly.html.haml
@@ -0,0 +1,27 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ = f.label :gitaly_timeout_default, 'Default Timeout Period', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :gitaly_timeout_default, class: 'form-control'
+ .help-block
+ Timeout for Gitaly calls from the GitLab application (in seconds). This timeout is not enforced
+ for git fetch/push operations or Sidekiq jobs.
+ .form-group
+ = f.label :gitaly_timeout_fast, 'Fast Timeout Period', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :gitaly_timeout_fast, class: 'form-control'
+ .help-block
+ Fast operation timeout (in seconds). Some Gitaly operations are expected to be fast.
+ If they exceed this threshold, there may be a problem with a storage shard and 'failing fast'
+ can help maintain the stability of the GitLab instance.
+ .form-group
+ = f.label :gitaly_timeout_medium, 'Medium Timeout Period', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :gitaly_timeout_medium, class: 'form-control'
+ .help-block
+ Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout.
+
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_koding.html.haml b/app/views/admin/application_settings/_koding.html.haml
new file mode 100644
index 00000000000..17358cf775b
--- /dev/null
+++ b/app/views/admin/application_settings/_koding.html.haml
@@ -0,0 +1,24 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :koding_enabled do
+ = f.check_box :koding_enabled
+ Enable Koding
+ .help-block
+ Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again.
+ .form-group
+ = f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
+ .help-block
+ Koding has integration enabled out of the box for the
+ %strong gitlab
+ team, and you need to provide that team's URL here. Learn more in the
+ = succeed "." do
+ = link_to "Koding administration documentation", help_page_path("administration/integration/koding")
+
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_performance.html.haml b/app/views/admin/application_settings/_performance.html.haml
new file mode 100644
index 00000000000..01d5a31aa9f
--- /dev/null
+++ b/app/views/admin/application_settings/_performance.html.haml
@@ -0,0 +1,19 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :authorized_keys_enabled do
+ = f.check_box :authorized_keys_enabled
+ Write to "authorized_keys" file
+ .help-block
+ By default, we write to the "authorized_keys" file to support Git
+ over SSH without additional configuration. GitLab can be optimized
+ to authenticate SSH keys via the database file. Only uncheck this
+ if you have configured your OpenSSH server to use the
+ AuthorizedKeysCommand. Click on the help icon for more details.
+ = link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
+
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_plantuml.html.haml b/app/views/admin/application_settings/_plantuml.html.haml
new file mode 100644
index 00000000000..56764b3fb81
--- /dev/null
+++ b/app/views/admin/application_settings/_plantuml.html.haml
@@ -0,0 +1,20 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :plantuml_enabled do
+ = f.check_box :plantuml_enabled
+ Enable PlantUML
+ .form-group
+ = f.label :plantuml_url, 'PlantUML URL', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080'
+ .help-block
+ Allow rendering of
+ = link_to "PlantUML", "http://plantuml.com"
+ diagrams in Asciidoc documents using an external PlantUML service.
+
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_realtime.html.haml b/app/views/admin/application_settings/_realtime.html.haml
new file mode 100644
index 00000000000..0a53a75119e
--- /dev/null
+++ b/app/views/admin/application_settings/_realtime.html.haml
@@ -0,0 +1,19 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ = f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :polling_interval_multiplier, class: 'form-control'
+ .help-block
+ Change this value to influence how frequently the GitLab UI polls for updates.
+ If you set the value to 2 all polling intervals are multiplied
+ by 2, which means that polling happens half as frequently.
+ The multiplier can also have a decimal value.
+ The default value (1) is a reasonable choice for the majority of GitLab
+ installations. Set to 0 to completely disable polling.
+ = link_to icon('question-circle'), help_page_path('administration/polling')
+
+ = f.submit 'Save changes', class: "btn btn-success"
+
diff --git a/app/views/admin/application_settings/_registry.html.haml b/app/views/admin/application_settings/_registry.html.haml
new file mode 100644
index 00000000000..3451ef62458
--- /dev/null
+++ b/app/views/admin/application_settings/_registry.html.haml
@@ -0,0 +1,10 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ = f.label :container_registry_token_expire_delay, 'Authorization token duration (minutes)', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :container_registry_token_expire_delay, class: 'form-control'
+
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_terminal.html.haml b/app/views/admin/application_settings/_terminal.html.haml
new file mode 100644
index 00000000000..36d8838803f
--- /dev/null
+++ b/app/views/admin/application_settings/_terminal.html.haml
@@ -0,0 +1,13 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ = f.label :terminal_max_session_time, 'Max session time', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :terminal_max_session_time, class: 'form-control'
+ .help-block
+ Maximum time for web terminal websocket connection (in seconds).
+ 0 for unlimited.
+
+ = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml
new file mode 100644
index 00000000000..7684e2cfdd1
--- /dev/null
+++ b/app/views/admin/application_settings/_usage.html.haml
@@ -0,0 +1,37 @@
+= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :version_check_enabled do
+ = f.check_box :version_check_enabled
+ Enable version check
+ .help-block
+ GitLab will inform you if a new version is available.
+ = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "version-check")
+ about what information is shared with GitLab Inc.
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ - can_be_configured = @application_setting.usage_ping_can_be_configured?
+ .checkbox
+ = f.label :usage_ping_enabled do
+ = f.check_box :usage_ping_enabled, disabled: !can_be_configured
+ Enable usage ping
+ .help-block
+ - if can_be_configured
+ To help improve GitLab and its user experience, GitLab will
+ periodically collect usage information.
+ = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "usage-ping")
+ about what information is shared with GitLab Inc. Visit
+ = link_to 'Cohorts', admin_cohorts_path(anchor: 'usage-ping')
+ to see the JSON payload sent.
+ - else
+ The usage ping is disabled, and cannot be configured through this
+ form. For more information, see the documentation on
+ = succeed '.' do
+ = link_to 'deactivating the usage ping', help_page_path('user/admin_area/settings/usage_statistics', anchor: 'deactivate-the-usage-ping')
+
+ = f.submit 'Save changes', class: "btn btn-success"
+
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index f4320513aff..caaa93aa1e2 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -76,7 +76,7 @@
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%p
- = _('Auto DevOps, runners amd job artifacts')
+ = _('Auto DevOps, runners and job artifacts')
.settings-content
= render 'ci_cd'
@@ -102,7 +102,7 @@
.settings-content
= render 'prometheus'
-%section.settings.as-performance.no-animate#js-performance-settings{ class: ('expanded' if expanded) }
+%section.settings.as-performance-bar.no-animate#js-performance-bar-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _('Profiling - Performance bar')
@@ -180,6 +180,107 @@
.settings-content
= render 'repository_check'
+- if Gitlab.config.registry.enabled
+ %section.settings.as-registry.no-animate#js-registry-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Container Registry')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Various container registry settings.')
+ .settings-content
+ = render 'registry'
+
+- if koding_enabled?
+ %section.settings.as-koding.no-animate#js-koding-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Koding')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Online IDE integration settings.')
+ .settings-content
+ = render 'koding'
+
+%section.settings.as-plantuml.no-animate#js-plantuml-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('PlantUML')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Allow rendering of PlantUML diagrams in Asciidoc documents.')
+ .settings-content
+ = render 'plantuml'
+
+%section.settings.as-usage.no-animate#js-usage-settings{ class: ('expanded' if expanded) }
+ .settings-header#usage-statistics
+ %h4
+ = _('Usage statistics')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Enable or disable version check and usage ping.')
+ .settings-content
+ = render 'usage'
+
+%section.settings.as-email.no-animate#js-email-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Email')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Various email settings.')
+ .settings-content
+ = render 'email'
+
+%section.settings.as-gitaly.no-animate#js-gitaly-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Gitaly')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Configure Gitaly timeouts.')
+ .settings-content
+ = render 'gitaly'
+
+%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Web terminal')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Set max session time for web terminal.')
+ .settings-content
+ = render 'terminal'
+
+%section.settings.as-realtime.no-animate#js-realtime-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Real-time features')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Change this value to influence how frequently the GitLab UI polls for updates.')
+ .settings-content
+ = render 'realtime'
+
+%section.settings.as-performance.no-animate#js-performance-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Performance optimization')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? 'Collapse' : 'Expand'
+ %p
+ = _('Various settings that affect GitLab performance.')
+ .settings-content
+ = render 'performance'
+
%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
@@ -201,6 +302,3 @@
= _('Allow requests to the local network from hooks and services.')
.settings-content
= render 'outbound'
-
-.prepend-top-20
- = render 'form'
diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml
index 35a3563dff1..5114387984b 100644
--- a/app/views/ci/status/_badge.html.haml
+++ b/app/views/ci/status/_badge.html.haml
@@ -4,10 +4,10 @@
- css_classes = "ci-status ci-#{status.group} #{'has-tooltip' if title.present?}"
- if link && status.has_details?
- = link_to status.details_path, class: css_classes, title: title do
+ = link_to status.details_path, class: css_classes, title: title, data: { html: title.present? } do
= sprite_icon(status.icon)
= status.text
- else
- %span{ class: css_classes, title: title }
+ %span{ class: css_classes, title: title, data: { html: title.present? } }
= sprite_icon(status.icon)
= status.text
diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml
index c5b4439e273..db2040110fa 100644
--- a/app/views/ci/status/_dropdown_graph_badge.html.haml
+++ b/app/views/ci/status/_dropdown_graph_badge.html.haml
@@ -3,14 +3,15 @@
- subject = local_assigns.fetch(:subject)
- status = subject.detailed_status(current_user)
- klass = "ci-status-icon ci-status-icon-#{status.group}"
-- tooltip = "#{subject.name} - #{status.label}"
+- tooltip = "#{subject.name} - #{status.status_tooltip}"
- if status.has_details?
- = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, container: 'body' } do
+ = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, html: true, container: 'body' } do
%span{ class: klass }= sprite_icon(status.icon)
%span.ci-build-text= subject.name
+
- else
- .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip, container: 'body' } }
+ .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', html: true, title: tooltip, container: 'body' } }
%span{ class: klass }= sprite_icon(status.icon)
%span.ci-build-text= subject.name
diff --git a/app/views/email_rejection_mailer/rejection.text.haml b/app/views/email_rejection_mailer/rejection.text.haml
index 6693e6f90e8..af518b5b583 100644
--- a/app/views/email_rejection_mailer/rejection.text.haml
+++ b/app/views/email_rejection_mailer/rejection.text.haml
@@ -1,4 +1,3 @@
Unfortunately, your email message to GitLab could not be processed.
-
-
+\
= @reason
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 78848542810..b96251cd982 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -19,8 +19,10 @@
%h5.prepend-top-0
Your New Personal Access Token
.form-group
- = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
- = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left")
+ .input-group
+ = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block"
+ %span.input-group-btn
+ = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left", class: "btn-default btn-clipboard")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index 825bfd0707f..1e7d9444986 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -21,11 +21,11 @@
%li Project uploads
%li Project configuration including web hooks and services
%li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
+ %li LFS objects
%p
The following items will NOT be exported:
%ul
%li Job traces and artifacts
- %li LFS objects
%li Container registry images
%li CI variables
%li Any encrypted tokens
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 461129a3e0e..74c5317428c 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -49,10 +49,10 @@
.commit-box{ data: { project_path: project_path(@project) } }
%h3.commit-title
- = markdown(@commit.title, pipeline: :single_line, author: @commit.author)
+ = markdown_field(@commit, :title)
- if @commit.description.present?
%pre.commit-description
- = preserve(markdown(@commit.description, pipeline: :single_line, author: @commit.author))
+ = preserve(markdown_field(@commit, :description))
.info-well
.well-segment.branch-info
diff --git a/app/views/projects/commits/_commit.atom.builder b/app/views/projects/commits/_commit.atom.builder
index 50f7e7a3a33..640b5ecf99e 100644
--- a/app/views/projects/commits/_commit.atom.builder
+++ b/app/views/projects/commits/_commit.atom.builder
@@ -10,5 +10,5 @@ xml.entry do
xml.email commit.author_email
end
- xml.summary markdown(commit.description, pipeline: :single_line), type: 'html'
+ xml.summary markdown_field(commit, :description), type: 'html'
end
diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml
index ecf186e3dc8..0b57ebedebd 100644
--- a/app/views/projects/jobs/_sidebar.html.haml
+++ b/app/views/projects/jobs/_sidebar.html.haml
@@ -1,5 +1,3 @@
-- builds = @build.pipeline.builds.to_a
-
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" } }
.sidebar-container
.blocks-container
@@ -91,7 +89,8 @@
- HasStatus::ORDERED_STATUSES.each do |build_status|
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } }
- = link_to project_job_path(@project, build) do
+ - tooltip = build.tooltip_message
+ = link_to(project_job_path(@project, build), data: { toggle: 'tooltip', html: true, title: tooltip, container: 'body' }) do
= sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right')
%span{ class: "ci-status-icon-#{build.status}" }
= ci_icon_for_status(build.status)
@@ -101,5 +100,4 @@
- else
= build.id
- if build.retried?
- %span.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Job was retried' }
- = sprite_icon('retry', size:16, css_class: 'icon-retry')
+ = sprite_icon('retry', size:16, css_class: 'icon-retry')
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 29f999da5d2..8beb4ffef45 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -92,7 +92,7 @@
- else
= render "empty_states"
- = render "sidebar"
+ = render "sidebar", builds: @builds
.js-build-options{ data: javascript_build_options }
diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
index 75df92b05a7..27bbe52a714 100644
--- a/app/views/projects/pages/_list.html.haml
+++ b/app/views/projects/pages/_list.html.haml
@@ -1,28 +1,29 @@
+- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
+
- if can?(current_user, :update_pages, @project) && @domains.any?
.panel.panel-default
.panel-heading
Domains (#{@domains.count})
- %ul.well-list
- - verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
+ %ul.well-list.pages-domain-list{ class: ("has-verification-status" if verification_enabled) }
- @domains.each do |domain|
- %li
- .pull-right
+ %li.pages-domain-list-item.unstyled
+ - if verification_enabled
+ - tooltip, status = domain.unverified? ? [_('Unverified'), 'failed'] : [_('Verified'), 'success']
+ .domain-status.ci-status-icon.has-tooltip{ class: "ci-status-icon-#{status}", title: tooltip }
+ = sprite_icon("status_#{status}", size: 16 )
+ .domain-name
+ = link_to domain.url do
+ = domain.url
+ = icon('external-link')
+ - if domain.subject
+ %p
+ %span.label.label-gray Certificate: #{domain.subject}
+ - if domain.expired?
+ %span.label.label-danger Expired
+ %div
= link_to 'Details', project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped"
= link_to 'Remove', project_pages_domain_path(@project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
- .clearfix
- - if verification_enabled
- - tooltip, status = domain.unverified? ? ['Unverified', 'failed'] : ['Verified', 'success']
- = link_to domain.url, title: tooltip, class: 'has-tooltip' do
- = sprite_icon("status_#{status}", size: 16, css_class: "has-tooltip ci-status-icon ci-status-icon-#{status}")
- = domain.domain
- - else
- = link_to domain.domain, domain.url
- %p
- - if domain.subject
- %span.label.label-gray Certificate: #{domain.subject}
- - if domain.expired?
- %span.label.label-danger Expired
- if verification_enabled && domain.unverified?
%li.warning-row
#{domain.domain} is not verified. To learn how to verify ownership, visit your
- = link_to 'domain details', project_pages_domain_path(@project, domain)
+ #{link_to 'domain details', project_pages_domain_path(@project, domain)}.
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index f17d9d24db6..6adaea799b2 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -1,11 +1,10 @@
- page_title 'Pages'
-%h3.page_title
+%h3.page-title.with-button
Pages
- if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https)
= link_to new_project_pages_domain_path(@project), class: 'btn btn-new pull-right', title: 'New Domain' do
- %i.fa.fa-plus
New Domain
%p.light
diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml
index 5645a4604bf..6c404990492 100644
--- a/app/views/projects/pages_domains/edit.html.haml
+++ b/app/views/projects/pages_domains/edit.html.haml
@@ -1,7 +1,7 @@
- add_to_breadcrumbs "Pages", project_pages_path(@project)
- breadcrumb_title @domain.domain
- page_title @domain.domain
-%h3.page_title
+%h3.page-title
= @domain.domain
%hr.clearfix
%div
diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml
index e49163880c7..269df803a2b 100644
--- a/app/views/projects/pages_domains/new.html.haml
+++ b/app/views/projects/pages_domains/new.html.haml
@@ -1,6 +1,6 @@
- add_to_breadcrumbs "Pages", project_pages_path(@project)
- page_title 'New Pages Domain'
-%h3.page_title
+%h3.page-title
New Pages Domain
%hr.clearfix
%div
diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml
index ba0713daee9..44d66f3b2d0 100644
--- a/app/views/projects/pages_domains/show.html.haml
+++ b/app/views/projects/pages_domains/show.html.haml
@@ -1,17 +1,19 @@
- add_to_breadcrumbs "Pages", project_pages_path(@project)
- breadcrumb_title @domain.domain
- page_title "#{@domain.domain}", 'Pages Domains'
+- dns_record = "#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}."
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
+
- if verification_enabled && @domain.unverified?
- %p.alert.alert-warning
- %strong
- This domain is not verified. You will need to verify ownership before
- access is enabled.
+ = content_for :flash_message do
+ .alert.alert-warning
+ .container-fluid.container-limited
+ This domain is not verified. You will need to verify ownership before access is enabled.
-%h3.page-title
- Pages Domain
+%h3.page-title.with-button
= link_to 'Edit', edit_project_pages_domain_path(@project, @domain), class: 'btn btn-success pull-right'
+ Pages Domain
.table-holder
%table.table
@@ -19,31 +21,41 @@
%td
Domain
%td
- = link_to @domain.domain, @domain.url
+ = link_to @domain.url do
+ = @domain.url
+ = icon('external-link')
%tr
%td
DNS
%td
- %p
- To access this domain create a new DNS record:
- %pre
- #{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}.
+ .input-group
+ = text_field_tag :domain_dns, dns_record , class: "monospace js-select-on-focus form-control", readonly: true
+ .input-group-btn
+ = clipboard_button(target: '#domain_dns', class: 'btn-default hidden-xs')
+ %p.help-block
+ To access this domain create a new DNS record
+
- if verification_enabled
+ - verification_record = "#{@domain.verification_domain} TXT #{@domain.keyed_verification_code}"
%tr
%td
Verification status
%td
- %p
+ = form_tag verify_project_pages_domain_path(@project, @domain) do
+ .status-badge
+ - text, status = @domain.unverified? ? [_('Unverified'), 'label-danger'] : [_('Verified'), 'label-success']
+ .label{ class: status }
+ = text
+ %button.btn.has-tooltip{ type: "submit", data: { container: 'body' }, title: _("Retry verification") }
+ = sprite_icon('redo')
+ .input-group
+ = text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
+ .input-group-btn
+ = clipboard_button(target: '#domain_verification', class: 'btn-default hidden-xs')
+ %p.help-block
- help_link = help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
- To #{link_to 'verify ownership', help_link} of your domain, create
- this DNS record:
- %pre
- #{@domain.verification_domain} TXT #{@domain.keyed_verification_code}
- %p
- - if @domain.verified?
- #{@domain.domain} has been successfully verified.
- - else
- = button_to 'Verify ownership', verify_project_pages_domain_path(@project, @domain), class: 'btn btn-save btn-sm'
+ To #{link_to 'verify ownership', help_link} of your domain,
+ add the above key to a TXT record within to your DNS configuration.
%tr
%td
diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/settings/ci_cd/_badge.html.haml
index e8028059487..e8028059487 100644
--- a/app/views/projects/pipelines_settings/_badge.html.haml
+++ b/app/views/projects/settings/ci_cd/_badge.html.haml
diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 646c01c0989..20868f9ba5d 100644
--- a/app/views/projects/pipelines_settings/_show.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -1,6 +1,7 @@
.row.prepend-top-default
.col-lg-12
- = form_for @project, url: project_pipelines_settings_path(@project) do |f|
+ = form_for @project, url: project_settings_ci_cd_path(@project) do |f|
+ = form_errors(@project)
%fieldset.builds-feature
.form-group
%h5 Auto DevOps (Beta)
@@ -73,10 +74,10 @@
%hr
.form-group
- = f.label :build_timeout_in_minutes, 'Timeout', class: 'label-light'
- = f.number_field :build_timeout_in_minutes, class: 'form-control', min: '0'
+ = f.label :build_timeout_human_readable, 'Timeout', class: 'label-light'
+ = f.text_field :build_timeout_human_readable, class: 'form-control'
%p.help-block
- Per job in minutes. If a job passes this threshold, it will be marked as failed
+ Per job. If a job passes this threshold, it will be marked as failed
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank'
%hr
@@ -151,10 +152,13 @@
%li
excoveralls (Elixir) -
%code \[TOTAL\]\s+(\d+\.\d+)%
+ %li
+ JaCoCo (Java/Kotlin)
+ %code Total.*?([0-9]{1,3})%
= f.submit 'Save changes', class: "btn btn-save"
%hr
.row.prepend-top-default
- = render partial: 'projects/pipelines_settings/badge', collection: @badges
+ = render partial: 'badge', collection: @badges
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index d65341dbd40..09268c9943b 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -3,8 +3,9 @@
- page_title "CI / CD"
- expanded = Rails.env.test?
+- general_expanded = @project.errors.empty? ? expanded : true
-%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if expanded) }
+%section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
.settings-header
%h4
General pipelines settings
@@ -13,7 +14,7 @@
%p
Update your CI/CD configuration, like job timeout or Auto DevOps.
.settings-content
- = render 'projects/pipelines_settings/show'
+ = render 'form'
%section.settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 6afcd447f28..975b9cb4729 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -107,7 +107,7 @@
- selected_labels.each do |label|
= hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (project_labels_path(@project, :json) if @project) } }
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path(false) if @project) } }
%span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) }
= multi_label_name(selected_labels, "Labels")
= icon('chevron-down', 'aria-hidden': 'true')
diff --git a/app/views/shared/notes/_form.html.haml b/app/views/shared/notes/_form.html.haml
index 71c0d740bc8..725bf916592 100644
--- a/app/views/shared/notes/_form.html.haml
+++ b/app/views/shared/notes/_form.html.haml
@@ -24,21 +24,20 @@
-# DiffNote
= f.hidden_field :position
- .discussion-form-container
- = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
- = render 'projects/zen', f: f,
- attr: :note,
- classes: 'note-textarea js-note-text',
- placeholder: "Write a comment or drag your files here...",
- supports_quick_actions: supports_quick_actions,
- supports_autocomplete: supports_autocomplete
- = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
- .error-alert
-
- .note-form-actions.clearfix
- = render partial: 'shared/notes/comment_button'
-
- = yield(:note_actions)
-
- %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } }
- Discard draft
+ = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
+ = render 'projects/zen', f: f,
+ attr: :note,
+ classes: 'note-textarea js-note-text',
+ placeholder: "Write a comment or drag your files here...",
+ supports_quick_actions: supports_quick_actions,
+ supports_autocomplete: supports_autocomplete
+ = render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
+ .error-alert
+
+ .note-form-actions.clearfix
+ = render partial: 'shared/notes/comment_button'
+
+ = yield(:note_actions)
+
+ %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } }
+ Discard draft
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index ad4d39b4aa1..d36ca032558 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -33,6 +33,13 @@
%p.light
This URL will be triggered when someone adds a comment
%li
+ = form.check_box :confidential_note_events, class: 'pull-left'
+ .prepend-left-20
+ = form.label :confidential_note_events, class: 'list-label' do
+ %strong Confidential Comments
+ %p.light
+ This URL will be triggered when someone adds a comment on a confidential issue
+ %li
= form.check_box :issues_events, class: 'pull-left'
.prepend-left-20
= form.label :issues_events, class: 'list-label' do
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
index 67c54fbf10e..b925741934a 100644
--- a/app/workers/new_note_worker.rb
+++ b/app/workers/new_note_worker.rb
@@ -5,7 +5,7 @@ class NewNoteWorker
# old `NewNoteWorker` jobs (can remove later)
def perform(note_id, _params = {})
if note = Note.find_by(id: note_id)
- NotificationService.new.new_note(note)
+ NotificationService.new.new_note(note) if note.can_create_notification?
Notes::PostProcessService.new(note).execute
else
Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")