summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue2
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-rw-r--r--app/assets/javascripts/diffs/components/app.vue12
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue16
-rw-r--r--app/assets/javascripts/diffs/constants.js3
-rw-r--r--app/assets/javascripts/diffs/store/actions.js21
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js43
-rw-r--r--app/assets/javascripts/ide/stores/mutations.js7
-rw-r--r--app/assets/javascripts/jobs/components/artifacts_block.vue98
-rw-r--r--app/assets/javascripts/jobs/components/empty_state.vue76
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue139
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/index.js2
-rw-r--r--app/assets/javascripts/project_select.js8
-rw-r--r--app/assets/stylesheets/framework/common.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss1
-rw-r--r--app/finders/license_template_finder.rb36
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/blob_helper.rb12
-rw-r--r--app/helpers/button_helper.rb6
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb5
-rw-r--r--app/mailers/abuse_report_mailer.rb2
-rw-r--r--app/mailers/base_mailer.rb2
-rw-r--r--app/mailers/devise_mailer.rb9
-rw-r--r--app/mailers/email_rejection_mailer.rb2
-rw-r--r--app/mailers/emails/issues.rb2
-rw-r--r--app/mailers/emails/members.rb2
-rw-r--r--app/mailers/emails/merge_requests.rb2
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/mailers/emails/pages_domains.rb2
-rw-r--r--app/mailers/emails/pipelines.rb8
-rw-r--r--app/mailers/emails/profile.rb2
-rw-r--r--app/mailers/emails/projects.rb2
-rw-r--r--app/mailers/notify.rb16
-rw-r--r--app/mailers/previews/devise_mailer_preview.rb2
-rw-r--r--app/mailers/previews/email_rejection_mailer_preview.rb2
-rw-r--r--app/mailers/previews/notify_preview.rb2
-rw-r--r--app/mailers/previews/repository_check_mailer_preview.rb2
-rw-r--r--app/mailers/repository_check_mailer.rb2
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/ci/build.rb5
-rw-r--r--app/models/license_template.rb53
-rw-r--r--app/models/namespace.rb2
-rw-r--r--app/models/project.rb8
-rw-r--r--app/models/project_auto_devops.rb6
-rw-r--r--app/services/groups/destroy_service.rb7
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml6
-rw-r--r--app/views/admin/application_settings/show.html.haml2
-rw-r--r--app/views/admin/users/_access_levels.html.haml12
-rw-r--r--app/views/admin/users/_form.html.haml33
-rw-r--r--app/views/instance_statistics/cohorts/_cohorts_table.html.haml2
-rw-r--r--app/views/instance_statistics/conversational_development_index/_no_data.html.haml2
-rw-r--r--app/views/instance_statistics/conversational_development_index/index.html.haml2
-rw-r--r--app/views/projects/mirrors/_instructions.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml4
-rw-r--r--app/views/projects/show.html.haml3
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/shared/runners/_form.html.haml30
-rw-r--r--app/workers/detect_repository_languages_worker.rb2
61 files changed, 664 insertions, 83 deletions
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 3e610a4088c..bfc8d9b03ad 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -203,7 +203,7 @@ export default {
this.showIssueForm = !this.showIssueForm;
},
onScroll() {
- if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
+ if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
this.loadNextPage();
}
},
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index 589eeee9695..742cf490ad2 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -8,6 +8,7 @@ import 'core-js/fn/object/assign';
import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
+import 'core-js/fn/string/includes';
import 'core-js/fn/symbol';
import 'core-js/es6/map';
import 'core-js/es6/weak-map';
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 7cc4e6a2c3a..b5b05df4d34 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -114,11 +114,15 @@ export default {
this.adjustView();
},
methods: {
- ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles']),
+ ...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles', 'startRenderDiffsQueue']),
fetchData() {
- this.fetchDiffFiles().catch(() => {
- createFlash(__('Something went wrong on our end. Please try again!'));
- });
+ this.fetchDiffFiles()
+ .then(() => {
+ requestIdleCallback(this.startRenderDiffsQueue, { timeout: 1000 });
+ })
+ .catch(() => {
+ createFlash(__('Something went wrong on our end. Please try again!'));
+ });
if (!this.isNotesFetched) {
eventHub.$emit('fetchNotesData');
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 7e7058d8d08..59e9ba08b8b 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -46,16 +46,25 @@ export default {
showExpandMessage() {
return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge;
},
+ showLoadingIcon() {
+ return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
+ },
},
methods: {
...mapActions('diffs', ['loadCollapsedDiff']),
handleToggle() {
const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file;
- if (collapsed && !highlightedDiffLines && !parallelDiffLines.length) {
+ if (
+ collapsed &&
+ !highlightedDiffLines &&
+ parallelDiffLines !== undefined &&
+ !parallelDiffLines.length
+ ) {
this.handleLoadCollapsedDiff();
} else {
this.file.collapsed = !this.file.collapsed;
+ this.file.renderIt = true;
}
},
handleLoadCollapsedDiff() {
@@ -65,6 +74,7 @@ export default {
.then(() => {
this.isLoadingCollapsedDiff = false;
this.file.collapsed = false;
+ this.file.renderIt = true;
})
.catch(() => {
this.isLoadingCollapsedDiff = false;
@@ -121,12 +131,12 @@ export default {
</div>
<diff-content
- v-if="!isCollapsed"
+ v-if="!isCollapsed && file.renderIt"
:class="{ hidden: isCollapsed || file.tooLarge }"
:diff-file="file"
/>
<loading-icon
- v-if="isLoadingCollapsedDiff"
+ v-else-if="showLoadingIcon"
class="diff-content loading"
/>
<div
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 2fa8367f528..f68afa44837 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -25,3 +25,6 @@ export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
export const UNFOLD_COUNT = 20;
export const COUNT_OF_AVATARS_IN_GUTTER = 3;
export const LENGTH_OF_AVATAR_TOOLTIP = 17;
+
+export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
+export const MAX_LINES_TO_BE_RENDERED = 2000;
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 27001142257..4ab6ceb249a 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -29,6 +29,27 @@ export const fetchDiffFiles = ({ state, commit }) => {
.then(handleLocationHash);
};
+export const startRenderDiffsQueue = ({ state, commit }) => {
+ const checkItem = () => {
+ const nextFile = state.diffFiles.find(
+ file => !file.renderIt && (!file.collapsed || !file.text),
+ );
+ if (nextFile) {
+ requestAnimationFrame(() => {
+ commit(types.RENDER_FILE, nextFile);
+ });
+ requestIdleCallback(
+ () => {
+ checkItem();
+ },
+ { timeout: 1000 },
+ );
+ }
+ };
+
+ checkItem();
+};
+
export const setInlineDiffViewType = ({ commit }) => {
commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE);
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 2c8e1a1466f..c999d637d50 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -8,3 +8,4 @@ export const REMOVE_COMMENT_FORM_LINE = 'REMOVE_COMMENT_FORM_LINE';
export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES';
export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS';
export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES';
+export const RENDER_FILE = 'RENDER_FILE';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index a98b2be89a3..0522e32c410 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -2,6 +2,7 @@ import Vue from 'vue';
import _ from 'underscore';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { findDiffFile, addLineReferences, removeMatchLine, addContextLines } from './utils';
+import { LINES_TO_BE_RENDERED_DIRECTLY, MAX_LINES_TO_BE_RENDERED } from '../constants';
import * as types from './mutation_types';
export default {
@@ -15,8 +16,48 @@ export default {
},
[types.SET_DIFF_DATA](state, data) {
+ const diffData = convertObjectPropsToCamelCase(data, { deep: true });
+ let showingLines = 0;
+ const filesLength = diffData.diffFiles.length;
+ let i;
+ for (i = 0; i < filesLength; i += 1) {
+ const file = diffData.diffFiles[i];
+ if (file.parallelDiffLines) {
+ const linesLength = file.parallelDiffLines.length;
+ let u = 0;
+ for (u = 0; u < linesLength; u += 1) {
+ const line = file.parallelDiffLines[u];
+ if (line.left) delete line.left.text;
+ if (line.right) delete line.right.text;
+ }
+ }
+
+ if (file.highlightedDiffLines) {
+ const linesLength = file.highlightedDiffLines.length;
+ let u;
+ for (u = 0; u < linesLength; u += 1) {
+ const line = file.highlightedDiffLines[u];
+ delete line.text;
+ }
+ }
+
+ if (file.highlightedDiffLines) {
+ showingLines += file.parallelDiffLines.length;
+ }
+ Object.assign(file, {
+ renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
+ collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
+ });
+ }
+
Object.assign(state, {
- ...convertObjectPropsToCamelCase(data, { deep: true }),
+ ...diffData,
+ });
+ },
+
+ [types.RENDER_FILE](state, file) {
+ Object.assign(file, {
+ renderIt: true,
});
},
diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js
index 1eda5768709..56a8d9430c7 100644
--- a/app/assets/javascripts/ide/stores/mutations.js
+++ b/app/assets/javascripts/ide/stores/mutations.js
@@ -200,6 +200,7 @@ export default {
},
[types.DELETE_ENTRY](state, path) {
const entry = state.entries[path];
+ const { tempFile = false } = entry;
const parent = entry.parentPath
? state.entries[entry.parentPath]
: state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
@@ -209,7 +210,11 @@ export default {
parent.tree = parent.tree.filter(f => f.path !== entry.path);
if (entry.type === 'blob') {
- state.changedFiles = state.changedFiles.concat(entry);
+ if (tempFile) {
+ state.changedFiles = state.changedFiles.filter(f => f.path !== path);
+ } else {
+ state.changedFiles = state.changedFiles.concat(entry);
+ }
}
},
[types.RENAME_ENTRY](state, { path, name, entryPath = null }) {
diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue
new file mode 100644
index 00000000000..525c5eec91a
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/artifacts_block.vue
@@ -0,0 +1,98 @@
+<script>
+ import TimeagoTooltiop from '~/vue_shared/components/time_ago_tooltip.vue';
+
+ export default {
+ components: {
+ TimeagoTooltiop,
+ },
+ props: {
+ // @build.artifacts_expired?
+ haveArtifactsExpired: {
+ type: Boolean,
+ required: true,
+ },
+ // @build.has_expiring_artifacts?
+ willArtifactsExpire: {
+ type: Boolean,
+ required: true,
+ },
+ expireAt: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ keepArtifactsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ downloadArtifactsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ browseArtifactsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ };
+</script>
+<template>
+ <div class="block">
+ <div class="title">
+ {{ s__('Job|Job artifacts') }}
+ </div>
+
+ <p
+ v-if="haveArtifactsExpired"
+ class="js-artifacts-removed build-detail-row"
+ >
+ {{ s__('Job|The artifacts were removed') }}
+ </p>
+ <p
+ v-else-if="willArtifactsExpire"
+ class="js-artifacts-will-be-removed build-detail-row"
+ >
+ {{ s__('Job|The artifacts will be removed') }}
+ </p>
+
+ <timeago-tooltiop
+ v-if="expireAt"
+ :time="expireAt"
+ />
+
+ <div
+ class="btn-group d-flex"
+ role="group"
+ >
+ <a
+ v-if="keepArtifactsPath"
+ :href="keepArtifactsPath"
+ class="js-keep-artifacts btn btn-sm btn-default"
+ data-method="post"
+ >
+ {{ s__('Job|Keep') }}
+ </a>
+
+ <a
+ v-if="downloadArtifactsPath"
+ :href="downloadArtifactsPath"
+ class="js-download-artifacts btn btn-sm btn-default"
+ download
+ rel="nofollow"
+ >
+ {{ s__('Job|Download') }}
+ </a>
+
+ <a
+ v-if="browseArtifactsPath"
+ :href="browseArtifactsPath"
+ class="js-browse-artifacts btn btn-sm btn-default"
+ >
+ {{ s__('Job|Browse') }}
+ </a>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
new file mode 100644
index 00000000000..4faf08387fb
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -0,0 +1,76 @@
+<script>
+ export default {
+ props: {
+ illustrationPath: {
+ type: String,
+ required: true,
+ },
+ illustrationSizeClass: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ content: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ action: {
+ type: Object,
+ required: false,
+ default: null,
+ validator(value) {
+ return (
+ value === null ||
+ (Object.prototype.hasOwnProperty.call(value, 'link') &&
+ Object.prototype.hasOwnProperty.call(value, 'method') &&
+ Object.prototype.hasOwnProperty.call(value, 'title'))
+ );
+ },
+ },
+ },
+ };
+</script>
+<template>
+ <div class="row empty-state">
+ <div class="col-12">
+ <div
+ :class="illustrationSizeClass"
+ class="svg-content"
+ >
+ <img :src="illustrationPath" />
+ </div>
+ </div>
+
+ <div class="col-12">
+ <div class="text-content">
+ <h4 class="js-job-empty-state-title text-center">
+ {{ title }}
+ </h4>
+
+ <p
+ v-if="content"
+ class="js-job-empty-state-content"
+ >
+ {{ content }}
+ </p>
+
+ <div
+ v-if="action"
+ class="text-center"
+ >
+ <a
+ :href="action.link"
+ :data-method="action.method"
+ class="js-job-empty-state-action btn btn-primary"
+ >
+ {{ action.title }}
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
new file mode 100644
index 00000000000..513851e376f
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -0,0 +1,139 @@
+<script>
+ import Icon from '~/vue_shared/components/icon.vue';
+ import tooltip from '~/vue_shared/directives/tooltip';
+ import { numberToHumanSize } from '~/lib/utils/number_utils';
+ import { s__, sprintf } from '~/locale';
+
+ export default {
+ components: {
+ Icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ canEraseJob: {
+ type: Boolean,
+ required: true,
+ },
+ size: {
+ type: Number,
+ required: true,
+ },
+ rawTracePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ canScrollToTop: {
+ type: Boolean,
+ required: true,
+ },
+ canScrollToBottom: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ jobLogSize() {
+ return sprintf('Showing last %{startSpanTag} %{size} %{endSpanTag} of log -', {
+ startSpanTag: '<span class="s-truncated-info-size truncated-info-size">',
+ endSpanTag: '</span>',
+ size: numberToHumanSize(this.size),
+ });
+ },
+ },
+ methods: {
+ handleEraseJobClick() {
+ // eslint-disable-next-line no-alert
+ if (window.confirm(s__('Job|Are you sure you want to erase this job?'))) {
+ this.$emit('eraseJob');
+ }
+ },
+ handleScrollToTop() {
+ this.$emit('scrollJobLogTop');
+ },
+ handleScrollToBottom() {
+ this.$emit('scrollJobLogBottom');
+ },
+ },
+ };
+</script>
+<template>
+ <div class="top-bar">
+ <!-- truncate information -->
+ <div class="js-truncated-info truncated-info d-none d-sm-block float-left">
+ <p v-html="jobLogSize"></p>
+
+ <a
+ v-if="rawTracePath"
+ :href="rawTracePath"
+ class="js-raw-link raw-link"
+ >
+ {{ s__("Job|Complete Raw") }}
+ </a>
+ </div>
+ <!-- eo truncate information -->
+
+ <div class="controllers float-right">
+ <!-- links -->
+ <a
+ v-tooltip
+ v-if="rawTracePath"
+ :title="s__('Job|Show complete raw')"
+ :href="rawTracePath"
+ class="js-raw-link-controller controllers-buttons"
+ data-container="body"
+ >
+ <icon name="doc-text" />
+ </a>
+
+ <button
+ v-tooltip
+ v-if="canEraseJob"
+ :title="s__('Job|Erase job log')"
+ type="button"
+ class="js-erase-link controllers-buttons"
+ data-container="body"
+ @click="handleEraseJobClick"
+ >
+ <icon name="remove" />
+ </button>
+ <!-- eo links -->
+
+ <!-- scroll buttons -->
+ <div
+ v-tooltip
+ :title="s__('Job|Scroll to top')"
+ class="controllers-buttons"
+ data-container="body"
+ >
+ <button
+ :disabled="!canScrollToTop"
+ type="button"
+ class="js-scroll-top btn-scroll btn-transparent btn-blank"
+ @click="handleScrollToTop"
+ >
+ <icon name="scroll_up"/>
+ </button>
+ </div>
+
+ <div
+ v-tooltip
+ :title="s__('Job|Scroll to bottom')"
+ class="controllers-buttons"
+ data-container="body"
+ >
+ <button
+ :disabled="!canScrollToBottom"
+ type="button"
+ class="js-scroll-bottom btn-scroll btn-transparent btn-blank"
+ @click="handleScrollToBottom"
+ >
+ <icon name="scroll_down"/>
+ </button>
+ </div>
+ <!-- eo scroll buttons -->
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js
index 48d75f5443b..47bd70537f1 100644
--- a/app/assets/javascripts/pages/admin/application_settings/index.js
+++ b/app/assets/javascripts/pages/admin/application_settings/index.js
@@ -1,6 +1,8 @@
import initSettingsPanels from '~/settings_panels';
+import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
+ projectSelect();
});
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index bce7556bd40..6f3b32f8eea 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -14,6 +14,7 @@ export default function projectSelect() {
this.orderBy = $(select).data('orderBy') || 'id';
this.withIssuesEnabled = $(select).data('withIssuesEnabled');
this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled');
+ this.allowClear = $(select).data('allowClear') || false;
placeholder = "Search for project";
if (this.includeGroups) {
@@ -71,6 +72,13 @@ export default function projectSelect() {
text: function (project) {
return project.name_with_namespace || project.name;
},
+
+ initSelection: function(el, callback) {
+ return Api.project(el.val()).then(({ data }) => callback(data));
+ },
+
+ allowClear: this.allowClear,
+
dropdownCssClass: "ajax-project-dropdown"
});
if (simpleFilter) return select;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 268e68dbb15..48a87ea8616 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -117,6 +117,10 @@ hr {
color: $blue-600;
}
+.author-link:hover {
+ text-decoration: none;
+}
+
.back-link {
font-size: 14px;
}
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index af91497d0ea..eac1345742d 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -1293,6 +1293,7 @@ $ide-tree-text-start: $ide-activity-bar-width + $ide-tree-padding;
&.build-page .top-bar {
top: 0;
+ height: auto;
font-size: 12px;
border-top-right-radius: $border-radius-default;
}
diff --git a/app/finders/license_template_finder.rb b/app/finders/license_template_finder.rb
new file mode 100644
index 00000000000..fad33f0eca2
--- /dev/null
+++ b/app/finders/license_template_finder.rb
@@ -0,0 +1,36 @@
+# LicenseTemplateFinder
+#
+# Used to find license templates, which may come from a variety of external
+# sources
+#
+# Arguments:
+# popular: boolean. When set to true, only "popular" licenses are shown. When
+# false, all licenses except popular ones are shown. When nil (the
+# default), *all* licenses will be shown.
+class LicenseTemplateFinder
+ attr_reader :params
+
+ def initialize(params = {})
+ @params = params
+ end
+
+ def execute
+ Licensee::License.all(featured: popular_only?).map do |license|
+ LicenseTemplate.new(
+ id: license.key,
+ name: license.name,
+ nickname: license.nickname,
+ category: (license.featured? ? :Popular : :Other),
+ content: license.content,
+ url: license.url,
+ meta: license.meta
+ )
+ end
+ end
+
+ private
+
+ def popular_only?
+ params.fetch(:popular, nil)
+ end
+end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 2bdf2c2c120..1e05f07e676 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -254,6 +254,7 @@ module ApplicationSettingsHelper
:usage_ping_enabled,
:instance_statistics_visibility_private,
:user_default_external,
+ :user_show_add_ssh_key_message,
:user_oauth_applications,
:version_check_enabled,
:web_ide_clientside_preview_enabled
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 7eb45ddd117..b61cbd5418a 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -182,12 +182,14 @@ module BlobHelper
def licenses_for_select
return @licenses_for_select if defined?(@licenses_for_select)
- licenses = Licensee::License.all
+ grouped_licenses = LicenseTemplateFinder.new.execute.group_by(&:category)
+ categories = grouped_licenses.keys
- @licenses_for_select = {
- Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } },
- Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } }
- }
+ @licenses_for_select = categories.each_with_object({}) do |category, hash|
+ hash[category] = grouped_licenses[category].map do |license|
+ { name: license.name, id: license.id }
+ end
+ end
end
def ref_project
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 0171a880164..7adc882bc47 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -73,7 +73,11 @@ module ButtonHelper
end
def ssh_clone_button(project, append_link: true)
- dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile") if current_user.try(:require_ssh_key?)
+ if Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
+ current_user.try(:require_ssh_key?)
+ dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile")
+ end
+
append_url = project.ssh_url_to_repo if append_link
dropdown_item_with_description('SSH', dropdown_description, href: append_url)
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 41084ec686f..a8a10c98d69 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -62,6 +62,8 @@ module IconsHelper
names = "key"
when "two-factor"
names = "key"
+ when "google_oauth2"
+ names = "google"
end
options.include?(:base) ? fa_stacked_icon(names, options) : fa_icon(names, options)
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index aaf9dff43ee..6b4079b4113 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -192,7 +192,10 @@ module ProjectsHelper
end
def show_no_ssh_key_message?
- cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
+ Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
+ cookies[:hide_no_ssh_message].blank? &&
+ !current_user.hide_no_ssh_key &&
+ current_user.require_ssh_key?
end
def show_no_password_message?
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
index fe5f68ba3d5..e032f568913 100644
--- a/app/mailers/abuse_report_mailer.rb
+++ b/app/mailers/abuse_report_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class AbuseReportMailer < BaseMailer
def notify(abuse_report_id)
return unless deliverable?
diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb
index 654468bc7fe..5fd209c4761 100644
--- a/app/mailers/base_mailer.rb
+++ b/app/mailers/base_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class BaseMailer < ActionMailer::Base
around_action :render_with_default_locale
diff --git a/app/mailers/devise_mailer.rb b/app/mailers/devise_mailer.rb
index 962570a0efd..7aa75ee30e6 100644
--- a/app/mailers/devise_mailer.rb
+++ b/app/mailers/devise_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DeviseMailer < Devise::Mailer
default from: "#{Gitlab.config.gitlab.email_display_name} <#{Gitlab.config.gitlab.email_from}>"
default reply_to: Gitlab.config.gitlab.email_reply_to
@@ -9,8 +11,9 @@ class DeviseMailer < Devise::Mailer
protected
def subject_for(key)
- subject = super
- subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
- subject
+ subject = [super]
+ subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present?
+
+ subject.join(' | ')
end
end
diff --git a/app/mailers/email_rejection_mailer.rb b/app/mailers/email_rejection_mailer.rb
index 76db31a4c45..45fc5a6c383 100644
--- a/app/mailers/email_rejection_mailer.rb
+++ b/app/mailers/email_rejection_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EmailRejectionMailer < BaseMailer
def rejection(reason, original_raw, can_retry = false)
@reason = reason
diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb
index 392cc0bee03..c8b1ab5033a 100644
--- a/app/mailers/emails/issues.rb
+++ b/app/mailers/emails/issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Issues
def new_issue_email(recipient_id, issue_id, reason = nil)
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 75cf56a51f2..91dfdf58982 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Members
extend ActiveSupport::Concern
diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb
index 70509e9066d..70f65d4e58d 100644
--- a/app/mailers/emails/merge_requests.rb
+++ b/app/mailers/emails/merge_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module MergeRequests
def new_merge_request_email(recipient_id, merge_request_id, reason = nil)
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index d9a6fe2a41e..d3284e90568 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Notes
def note_commit_email(recipient_id, note_id)
diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb
index 0027dfdc36b..ce449237ef6 100644
--- a/app/mailers/emails/pages_domains.rb
+++ b/app/mailers/emails/pages_domains.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module PagesDomains
def pages_domain_enabled_email(domain, recipient)
diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb
index f9f45ab987b..31e183640ad 100644
--- a/app/mailers/emails/pipelines.rb
+++ b/app/mailers/emails/pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Pipelines
def pipeline_success_email(pipeline, recipients)
@@ -39,10 +41,10 @@ module Emails
end
def pipeline_subject(status)
- commit = @pipeline.short_sha
- commit << " in #{@merge_request.to_reference}" if @merge_request
+ commit = [@pipeline.short_sha]
+ commit << "in #{@merge_request.to_reference}" if @merge_request
- subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit)
+ subject("Pipeline ##{@pipeline.id} has #{status} for #{@pipeline.ref}", commit.join(' '))
end
end
end
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index 4f5edeb9bda..40d7b9ccd7a 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Profile
def new_user_email(user_id, token = nil)
diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 761d873c01c..d7e6c2ba7b2 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Emails
module Projects
def project_was_moved_email(project_id, user_id, old_path_with_namespace)
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0e1e39501f5..f4eeb85270e 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes
include GitlabRoutingHelper
@@ -92,12 +94,14 @@ class Notify < BaseMailer
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "Lorem ipsum | Dolor sit amet"
def subject(*extra)
- subject = ""
- subject << "#{@project.name} | " if @project
- subject << "#{@group.name} | " if @group
- subject << extra.join(' | ') if extra.present?
- subject << " | #{Gitlab.config.gitlab.email_subject_suffix}" if Gitlab.config.gitlab.email_subject_suffix.present?
- subject
+ subject = []
+
+ subject << @project.name if @project
+ subject << @group.name if @group
+ subject.concat(extra) if extra.present?
+ subject << Gitlab.config.gitlab.email_subject_suffix if Gitlab.config.gitlab.email_subject_suffix.present?
+
+ subject.join(' | ')
end
# Return a string suitable for inclusion in the 'Message-Id' mail header.
diff --git a/app/mailers/previews/devise_mailer_preview.rb b/app/mailers/previews/devise_mailer_preview.rb
index d6588efc486..3b9ef0d3ac0 100644
--- a/app/mailers/previews/devise_mailer_preview.rb
+++ b/app/mailers/previews/devise_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class DeviseMailerPreview < ActionMailer::Preview
def confirmation_instructions_for_signup
DeviseMailer.confirmation_instructions(unsaved_user, 'faketoken', {})
diff --git a/app/mailers/previews/email_rejection_mailer_preview.rb b/app/mailers/previews/email_rejection_mailer_preview.rb
index 639e8471232..402066151ef 100644
--- a/app/mailers/previews/email_rejection_mailer_preview.rb
+++ b/app/mailers/previews/email_rejection_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class EmailRejectionMailerPreview < ActionMailer::Preview
def rejection
EmailRejectionMailer.rejection("some rejection reason", "From: someone@example.com\nraw email here").message
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 3615cde8026..df470930e9e 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class NotifyPreview < ActionMailer::Preview
def note_merge_request_email_for_individual_note
note_email(:note_merge_request_email) do
diff --git a/app/mailers/previews/repository_check_mailer_preview.rb b/app/mailers/previews/repository_check_mailer_preview.rb
index 19d4eab1805..834d7594719 100644
--- a/app/mailers/previews/repository_check_mailer_preview.rb
+++ b/app/mailers/previews/repository_check_mailer_preview.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryCheckMailerPreview < ActionMailer::Preview
def notify
RepositoryCheckMailer.notify(3).message
diff --git a/app/mailers/repository_check_mailer.rb b/app/mailers/repository_check_mailer.rb
index 22a9f5da646..4bcf371cfc0 100644
--- a/app/mailers/repository_check_mailer.rb
+++ b/app/mailers/repository_check_mailer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class RepositoryCheckMailer < BaseMailer
def notify(failed_count)
@message =
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index bbe7811841a..c77faa4b71d 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -298,7 +298,8 @@ class ApplicationSetting < ActiveRecord::Base
unique_ips_limit_time_window: 3600,
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
instance_statistics_visibility_private: false,
- user_default_external: false
+ user_default_external: false,
+ user_show_add_ssh_key_message: true
}
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 3c69677baf0..faa160ad6ba 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -67,6 +67,10 @@ module Ci
'', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').archive)
end
+ scope :with_archived_trace, ->() do
+ where('EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
+ end
+
scope :without_archived_trace, ->() do
where('NOT EXISTS (?)', Ci::JobArtifact.select(1).where('ci_builds.id = ci_job_artifacts.job_id').trace)
end
@@ -77,6 +81,7 @@ module Ci
end
scope :with_artifacts_stored_locally, -> { with_artifacts_archive.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
+ scope :with_archived_trace_stored_locally, -> { with_archived_trace.where(artifacts_file_store: [nil, LegacyArtifactUploader::Store::LOCAL]) }
scope :with_artifacts_not_expired, ->() { with_artifacts_archive.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) }
scope :with_expired_artifacts, ->() { with_artifacts_archive.where('artifacts_expire_at < ?', Time.now) }
scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) }
diff --git a/app/models/license_template.rb b/app/models/license_template.rb
new file mode 100644
index 00000000000..0ad75b27827
--- /dev/null
+++ b/app/models/license_template.rb
@@ -0,0 +1,53 @@
+class LicenseTemplate
+ PROJECT_TEMPLATE_REGEX =
+ %r{[\<\{\[]
+ (project|description|
+ one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here
+ [\>\}\]]}xi.freeze
+ YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze
+ FULLNAME_TEMPLATE_REGEX =
+ %r{[\<\{\[]
+ (fullname|name\sof\s(author|copyright\sowner))
+ [\>\}\]]}xi.freeze
+
+ attr_reader :id, :name, :category, :nickname, :url, :meta
+
+ alias_method :key, :id
+
+ def initialize(id:, name:, category:, content:, nickname: nil, url: nil, meta: {})
+ @id = id
+ @name = name
+ @category = category
+ @content = content
+ @nickname = nickname
+ @url = url
+ @meta = meta
+ end
+
+ def popular?
+ category == :Popular
+ end
+ alias_method :featured?, :popular?
+
+ # Returns the text of the license
+ def content
+ if @content.respond_to?(:call)
+ @content = @content.call
+ else
+ @content
+ end
+ end
+
+ # Populate placeholders in the LicenseTemplate content
+ def resolve!(project_name: nil, fullname: nil, year: Time.now.year.to_s)
+ # Ensure the string isn't shared with any other instance of LicenseTemplate
+ new_content = content.dup
+ new_content.gsub!(YEAR_TEMPLATE_REGEX, year) if year.present?
+ new_content.gsub!(PROJECT_TEMPLATE_REGEX, project_name) if project_name.present?
+ new_content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname.present?
+
+ @content = new_content
+
+ self
+ end
+end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b974309aeb6..0deb44d7916 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -10,6 +10,7 @@ class Namespace < ActiveRecord::Base
include Storage::LegacyNamespace
include Gitlab::SQL::Pattern
include IgnorableColumn
+ include FeatureGate
ignore_column :deleted_at
@@ -124,7 +125,6 @@ class Namespace < ActiveRecord::Base
def to_param
full_path
end
- alias_method :flipper_id, :to_param
def human_name
owner_name
diff --git a/app/models/project.rb b/app/models/project.rb
index 7735f23cb9e..94c1d60f071 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -27,6 +27,7 @@ class Project < ActiveRecord::Base
include FastDestroyAll::Helpers
include WithUploads
include BatchDestroyDependentAssociations
+ include FeatureGate
extend Gitlab::Cache::RequestCache
extend Gitlab::ConfigHelper
@@ -519,18 +520,19 @@ class Project < ActiveRecord::Base
def auto_devops_enabled?
if auto_devops&.enabled.nil?
- Gitlab::CurrentSettings.auto_devops_enabled?
+ has_auto_devops_implicitly_enabled?
else
auto_devops.enabled?
end
end
def has_auto_devops_implicitly_enabled?
- auto_devops&.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?
+ auto_devops&.enabled.nil? &&
+ (Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
end
def has_auto_devops_implicitly_disabled?
- auto_devops&.enabled.nil? && !Gitlab::CurrentSettings.auto_devops_enabled?
+ auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
end
def empty_repo?
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 155400d1a43..dc6736dd9cd 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -47,12 +47,8 @@ class ProjectAutoDevops < ActiveRecord::Base
end
def needs_to_create_deploy_token?
- auto_devops_enabled? &&
+ project.auto_devops_enabled? &&
!project.public? &&
!project.deploy_tokens.find_by(name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME).present?
end
-
- def auto_devops_enabled?
- Gitlab::CurrentSettings.auto_devops_enabled? || enabled?
- end
end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index c4554ce45fb..12aeba4af71 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -2,6 +2,8 @@
module Groups
class DestroyService < Groups::BaseService
+ DestroyError = Class.new(StandardError)
+
def async_execute
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
@@ -12,9 +14,8 @@ module Groups
group.projects.each do |project|
# Execute the destruction of the models immediately to ensure atomic cleanup.
- # Skip repository removal because we remove directory with namespace
- # that contain all these repositories
- ::Projects::DestroyService.new(project, current_user, skip_repo: project.legacy_storage?).execute
+ success = ::Projects::DestroyService.new(project, current_user).execute
+ raise DestroyError, "Project #{project.id} can't be deleted" unless success
end
group.children.each do |group|
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 97f181ccea8..e390d7a04c3 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -30,7 +30,7 @@ module Projects
def run_auto_devops_pipeline?
return false if project.repository.gitlab_ci_yml || !project.auto_devops&.previous_changes&.include?('enabled')
- project.auto_devops.enabled? || (project.auto_devops.enabled.nil? && Gitlab::CurrentSettings.auto_devops_enabled?)
+ project.auto_devops_enabled?
end
private
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index 7c8243a7a90..622cb11010e 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -29,5 +29,11 @@
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
Newly registered users will by default be external
+ .form-group
+ = f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
+ .form-check
+ = f.check_box :user_show_add_ssh_key_message, class: 'form-check-input'
+ = f.label :user_show_add_ssh_key_message, class: 'form-check-label' do
+ Inform users without uploaded SSH keys that they can't push over SSH until one is added
= 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 258d50ad676..6133a7646f4 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -325,6 +325,8 @@
.settings-content
= render partial: 'repository_mirrors_form'
+= render_if_exists 'admin/application_settings/templates', expanded: expanded
+
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index 04acc5f8423..5f68163163e 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -1,15 +1,18 @@
%fieldset
%legend Access
.form-group.row
- = f.label :projects_limit, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :projects_limit, class: 'col-form-label'
.col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control'
.form-group.row
- = f.label :can_create_group, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :can_create_group, class: 'col-form-label'
.col-sm-10= f.check_box :can_create_group
.form-group.row
- = f.label :access_level, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :access_level, class: 'col-form-label'
.col-sm-10
- editing_current_user = (current_user == @user)
@@ -29,7 +32,8 @@
You cannot remove your own admin rights.
.form-group.row
- = f.label :external, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :external, class: 'col-form-label'
.col-sm-10
= f.check_box :external do
External
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 58be07fc83e..7f21bdb91c8 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -5,17 +5,20 @@
%fieldset
%legend Account
.form-group.row
- = f.label :name, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :name, class: 'col-form-label'
.col-sm-10
= f.text_field :name, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
.form-group.row
- = f.label :username, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :username, class: 'col-form-label'
.col-sm-10
= f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control'
%span.help-inline * required
.form-group.row
- = f.label :email, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :email, class: 'col-form-label'
.col-sm-10
= f.text_field :email, required: true, autocomplete: 'off', class: 'form-control'
%span.help-inline * required
@@ -24,7 +27,8 @@
%fieldset
%legend Password
.form-group.row
- = f.label :password, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :password, class: 'col-form-label'
.col-sm-10
%strong
Reset link will be generated and sent to the user.
@@ -34,10 +38,12 @@
%fieldset
%legend Password
.form-group.row
- = f.label :password, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :password, class: 'col-form-label'
.col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control'
.form-group.row
- = f.label :password_confirmation, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :password_confirmation, class: 'col-form-label'
.col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control'
= render partial: 'access_levels', locals: { f: f }
@@ -45,21 +51,26 @@
%fieldset
%legend Profile
.form-group.row
- = f.label :avatar, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :avatar, class: 'col-form-label'
.col-sm-10
= f.file_field :avatar
.form-group.row
- = f.label :skype, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :skype, class: 'col-form-label'
.col-sm-10= f.text_field :skype, class: 'form-control'
.form-group.row
- = f.label :linkedin, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :linkedin, class: 'col-form-label'
.col-sm-10= f.text_field :linkedin, class: 'form-control'
.form-group.row
- = f.label :twitter, class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :twitter, class: 'col-form-label'
.col-sm-10= f.text_field :twitter, class: 'form-control'
.form-group.row
- = f.label :website_url, 'Website', class: 'col-form-label col-sm-2'
+ .col-sm-2.text-right
+ = f.label :website_url, 'Website', class: 'col-form-label'
.col-sm-10= f.text_field :website_url, class: 'form-control'
.form-actions
diff --git a/app/views/instance_statistics/cohorts/_cohorts_table.html.haml b/app/views/instance_statistics/cohorts/_cohorts_table.html.haml
index 701a4e62b39..6a7c999bff3 100644
--- a/app/views/instance_statistics/cohorts/_cohorts_table.html.haml
+++ b/app/views/instance_statistics/cohorts/_cohorts_table.html.haml
@@ -3,7 +3,7 @@
User cohorts are shown for the last #{@cohorts[:months_included]}
months. Only users with activity are counted in the cohort total; inactive
users are counted separately.
- = link_to icon('question-circle'), help_page_path('user/admin_area/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank'
+ = link_to icon('question-circle'), help_page_path('user/instance_statistics/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank'
.table-holder
%table.table
diff --git a/app/views/instance_statistics/conversational_development_index/_no_data.html.haml b/app/views/instance_statistics/conversational_development_index/_no_data.html.haml
index d69c46194b4..dd795aee135 100644
--- a/app/views/instance_statistics/conversational_development_index/_no_data.html.haml
+++ b/app/views/instance_statistics/conversational_development_index/_no_data.html.haml
@@ -4,4 +4,4 @@
%h4 Data is still calculating...
%p
In order to gather accurate feature usage data, it can take 1 to 2 weeks to see your index.
- = link_to 'Learn more', help_page_path('user/admin_area/monitoring/convdev'), target: '_blank'
+ = link_to 'Learn more', help_page_path('user/instance_statistics/convdev'), target: '_blank'
diff --git a/app/views/instance_statistics/conversational_development_index/index.html.haml b/app/views/instance_statistics/conversational_development_index/index.html.haml
index e3d1aa31dc2..dd63b98376f 100644
--- a/app/views/instance_statistics/conversational_development_index/index.html.haml
+++ b/app/views/instance_statistics/conversational_development_index/index.html.haml
@@ -19,7 +19,7 @@
index
%br
score
- = link_to icon('question-circle', 'aria-hidden' => 'true'), help_page_path('user/admin_area/monitoring/convdev')
+ = link_to icon('question-circle', 'aria-hidden' => 'true'), help_page_path('user/instance_statistics/convdev')
.convdev-cards.board-card-container
- @metric.cards.each do |card|
diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml
index 3d811be3fe3..e051f9e6331 100644
--- a/app/views/projects/mirrors/_instructions.html.haml
+++ b/app/views/projects/mirrors/_instructions.html.haml
@@ -4,7 +4,7 @@
= _('The repository must be accessible over <code>http://</code>,
<code>https://</code>, <code>ssh://</code> and <code>git://</code>.').html_safe
%li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe
- %li= _('The update action will time out after 15 minutes. For big repositories, use a clone/push combination.')
+ %li= _("The update action will time out after #{import_will_timeout_message(Gitlab.config.gitlab_shell.git_timeout)} minutes. For big repositories, use a clone/push combination.")
%li= _('The Git LFS objects will <strong>not</strong> be synced.').html_safe
%li
= _('This user will be the author of all events in the activity feed that are the result of an update,
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 434aed2f603..9134257b631 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -17,7 +17,7 @@
%h5.prepend-top-0
= _("Git strategy for pipelines")
%p
- = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code")
+ = _("Choose between <code>clone</code> or <code>fetch</code> to get the recent application code").html_safe
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank'
.form-check
= f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' }
@@ -47,7 +47,7 @@
= f.label :ci_config_path, _('Custom CI config path'), class: 'label-bold'
= f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
%p.form-text.text-muted
- = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>")
+ = _("The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>").html_safe
= link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank'
%hr
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 8a5abb64515..df8a5742450 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -21,8 +21,7 @@
%nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
- - if Feature.enabled?(:repository_languages, @project.namespace.becomes(Namespace))
- = repository_languages_bar(@project.repository_languages)
+ = repository_languages_bar(@project.repository_languages)
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
- if @project.archived?
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index fdcd126e7a3..a8d4d4af93a 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,4 +1,6 @@
- project = find_project_for_result_blob(blob)
+- return unless project
+
- file_name, blob = parse_search_result(blob)
- blob_link = project_blob_path(project, tree_join(blob.ref, file_name))
diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml
index 0337680d79b..fa93307be31 100644
--- a/app/views/shared/runners/_form.html.haml
+++ b/app/views/shared/runners/_form.html.haml
@@ -1,56 +1,56 @@
= form_for runner, url: runner_form_url do |f|
= form_errors(runner)
.form-group.row
- = label :active, "Active", class: 'col-form-label col-sm-2'
+ = label :active, _("Active"), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :active, { class: 'form-check-input' }
- %span.light Paused Runners don't accept new jobs
+ %label.light{ for: :runner_active }= _("Paused Runners don't accept new jobs")
.form-group.row
- = label :protected, "Protected", class: 'col-form-label col-sm-2'
+ = label :protected, _("Protected"), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :access_level, { class: 'form-check-input' }, 'ref_protected', 'not_protected'
- %span.light This runner will only run on pipelines triggered on protected branches
+ %label.light{ for: :runner_access_level }= _('This runner will only run on pipelines triggered on protected branches')
.form-group.row
- = label :run_untagged, 'Run untagged jobs', class: 'col-form-label col-sm-2'
+ = label :run_untagged, _('Run untagged jobs'), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :run_untagged, { class: 'form-check-input' }
- %span.light Indicates whether this runner can pick jobs without tags
+ %label.light{ for: :runner_run_untagged }= _('Indicates whether this runner can pick jobs without tags')
- unless runner.group_type?
.form-group.row
= label :locked, _('Lock to current projects'), class: 'col-form-label col-sm-2'
.col-sm-10
.form-check
= f.check_box :locked, { class: 'form-check-input' }
- %span.light= _('When a runner is locked, it cannot be assigned to other projects')
+ %label.light{ for: :runner_locked }= _('When a runner is locked, it cannot be assigned to other projects')
.form-group.row
= label_tag :token, class: 'col-form-label col-sm-2' do
- Token
+ = _('Token')
.col-sm-10
= f.text_field :token, class: 'form-control', readonly: true
.form-group.row
= label_tag :ip_address, class: 'col-form-label col-sm-2' do
- IP Address
+ = _('IP Address')
.col-sm-10
= f.text_field :ip_address, class: 'form-control', readonly: true
.form-group.row
= label_tag :description, class: 'col-form-label col-sm-2' do
- Description
+ = _('Description')
.col-sm-10
= f.text_field :description, class: 'form-control'
.form-group.row
= label_tag :maximum_timeout_human_readable, class: 'col-form-label col-sm-2' do
- Maximum job timeout
+ = _('Maximum job timeout')
.col-sm-10
= f.text_field :maximum_timeout_human_readable, class: 'form-control'
- .form-text.text-muted This timeout will take precedence when lower than Project-defined timeout
+ .form-text.text-muted= _('This timeout will take precedence when lower than Project-defined timeout')
.form-group.row
= label_tag :tag_list, class: 'col-form-label col-sm-2' do
- Tags
+ = _('Tags')
.col-sm-10
= f.text_field :tag_list, value: runner.tag_list.sort.join(', '), class: 'form-control'
- .form-text.text-muted You can setup jobs to only use Runners with specific tags. Separate tags with commas.
+ .form-text.text-muted= _('You can setup jobs to only use Runners with specific tags. Separate tags with commas.')
.form-actions
- = f.submit 'Save changes', class: 'btn btn-save'
+ = f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb
index cfbbbffc8a6..854b74b884a 100644
--- a/app/workers/detect_repository_languages_worker.rb
+++ b/app/workers/detect_repository_languages_worker.rb
@@ -16,8 +16,6 @@ class DetectRepositoryLanguagesWorker
user = User.find_by(id: user_id)
return unless project && user
- return if Feature.disabled?(:repository_languages, project.namespace)
-
try_obtain_lease do
::Projects::DetectRepositoryLanguagesService.new(project, user).execute
end