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:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-13 15:08:41 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-13 15:08:41 +0300
commit6e91fbf77476011a7fd86ca3467aad6d7b110ff3 (patch)
treecace6db4e7ebef8b15a6a7fc8fbe8ff0d89bea90 /app
parent15ae4a8da83661f2b714d804721001a53b354d28 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/analytics/instance_statistics/utils.js40
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue121
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/form.vue15
-rw-r--r--app/assets/javascripts/ide/lib/errors.js38
-rw-r--r--app/assets/javascripts/ide/stores/getters.js6
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js18
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/mutations.js4
-rw-r--r--app/assets/javascripts/ide/utils.js28
-rw-r--r--app/assets/javascripts/incidents/components/incidents_list.vue10
-rw-r--r--app/assets/javascripts/incidents/constants.js9
-rw-r--r--app/assets/javascripts/issuable_show/components/issuable_description.vue31
-rw-r--r--app/assets/javascripts/issuable_show/components/issuable_edit_form.vue135
-rw-r--r--app/assets/javascripts/issuable_show/components/issuable_title.vue96
-rw-r--r--app/assets/javascripts/issuable_show/event_hub.js3
-rw-r--r--app/assets/javascripts/packages/details/components/composer_installation.vue56
-rw-r--r--app/assets/javascripts/packages/details/store/getters.js17
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue4
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue2
-rw-r--r--app/assets/javascripts/repository/index.js56
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/split_button.vue30
-rw-r--r--app/assets/stylesheets/_page_specific_files.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/error_tracking_index.scss (renamed from app/assets/stylesheets/pages/error_list.scss)8
-rw-r--r--app/assets/stylesheets/page_bundles/merge_conflicts.scss8
-rw-r--r--app/assets/stylesheets/pages/error_tracking_list.scss5
-rw-r--r--app/graphql/queries/repository/path_last_commit.query.graphql (renamed from app/assets/javascripts/repository/queries/path_last_commit.query.graphql)9
-rw-r--r--app/helpers/packages_helper.rb4
-rw-r--r--app/helpers/startupjs_helper.rb17
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/services/members/create_service.rb2
-rw-r--r--app/views/admin/dev_ops_report/show.html.haml2
-rw-r--r--app/views/layouts/_startup_js.html.haml22
-rw-r--r--app/views/projects/diffs/_text_file.html.haml2
-rw-r--r--app/views/projects/error_tracking/index.html.haml1
-rw-r--r--app/views/projects/packages/packages/show.html.haml3
-rw-r--r--app/views/projects/tree/show.html.haml2
36 files changed, 642 insertions, 174 deletions
diff --git a/app/assets/javascripts/analytics/instance_statistics/utils.js b/app/assets/javascripts/analytics/instance_statistics/utils.js
new file mode 100644
index 00000000000..30c6205b7ff
--- /dev/null
+++ b/app/assets/javascripts/analytics/instance_statistics/utils.js
@@ -0,0 +1,40 @@
+import { masks } from 'dateformat';
+import { formatDate } from '~/lib/utils/datetime_utility';
+
+const { isoDate } = masks;
+
+/**
+ * Takes an array of items and returns one item per month with the average of the `count`s from that month
+ * @param {Array} items
+ * @param {Number} items[index].count value to be averaged
+ * @param {String} items[index].recordedAt item dateTime time stamp to be collected into a month
+ * @param {Object} options
+ * @param {Object} options.shouldRound an option to specify whether the retuned averages should be rounded
+ * @return {Array} items collected into [month, average],
+ * where month is a dateTime string representing the first of the given month
+ * and average is the average of the count
+ */
+export function getAverageByMonth(items = [], options = {}) {
+ const { shouldRound = false } = options;
+ const itemsMap = items.reduce((memo, item) => {
+ const { count, recordedAt } = item;
+ const date = new Date(recordedAt);
+ const month = formatDate(new Date(date.getFullYear(), date.getMonth(), 1), isoDate);
+ if (memo[month]) {
+ const { sum, recordCount } = memo[month];
+ return { ...memo, [month]: { sum: sum + count, recordCount: recordCount + 1 } };
+ }
+
+ return { ...memo, [month]: { sum: count, recordCount: 1 } };
+ }, {});
+
+ return Object.keys(itemsMap).map(month => {
+ const { sum, recordCount } = itemsMap[month];
+ const avg = sum / recordCount;
+ if (shouldRound) {
+ return [month, Math.round(avg)];
+ }
+
+ return [month, avg];
+ });
+}
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index 09abdbe25d7..1b747fb7f20 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -122,67 +122,20 @@ export default {
</script>
<template>
- <li :class="{ 'js-toggle-container': collapsible }" class="commit flex-row">
- <div class="d-flex align-items-center align-self-start">
- <input
- v-if="isSelectable"
- class="mr-2"
- type="checkbox"
- :checked="checked"
- @change="$emit('handleCheckboxChange', $event.target.checked)"
- />
- <user-avatar-link
- :link-href="authorUrl"
- :img-src="authorAvatar"
- :img-alt="authorName"
- :img-size="40"
- class="avatar-cell d-none d-sm-block"
- />
- </div>
- <div class="commit-detail flex-list">
- <div class="commit-content qa-commit-content">
- <a
- :href="commit.commit_url"
- class="commit-row-message item-title"
- v-html="commit.title_html"
- ></a>
-
- <span class="commit-row-message d-block d-sm-none">&middot; {{ commit.short_id }}</span>
-
- <gl-button
- v-if="commit.description_html && collapsible"
- class="js-toggle-button"
- size="small"
- icon="ellipsis_h"
- :aria-label="__('Toggle commit description')"
- />
-
- <div class="committer">
- <a
- :href="authorUrl"
- :class="authorClass"
- :data-user-id="authorId"
- v-text="authorName"
- ></a>
- {{ s__('CommitWidget|authored') }}
- <time-ago-tooltip :time="commit.authored_date" />
- </div>
-
- <pre
- v-if="commit.description_html"
- :class="{ 'js-toggle-content': collapsible, 'd-block': !collapsible }"
- class="commit-row-description gl-mb-3 text-dark"
- v-html="commitDescription"
- ></pre>
- </div>
- <div class="commit-actions flex-row d-none d-sm-flex">
+ <li :class="{ 'js-toggle-container': collapsible }" class="commit">
+ <div
+ class="d-block d-sm-flex flex-row-reverse justify-content-between align-items-start flex-lg-row-reverse"
+ >
+ <div
+ class="commit-actions flex-row d-none d-sm-flex align-items-start flex-wrap justify-content-end"
+ >
<div v-if="commit.signature_html" v-html="commit.signature_html"></div>
<commit-pipeline-status
v-if="commit.pipeline_status_path"
:endpoint="commit.pipeline_status_path"
- class="d-inline-flex"
+ class="d-inline-flex mb-2"
/>
- <gl-button-group class="gl-ml-4" data-testid="commit-sha-group">
+ <gl-button-group class="gl-ml-4 gl-mb-4" data-testid="commit-sha-group">
<gl-button label class="gl-font-monospace" v-text="commit.short_id" />
<clipboard-button
:text="commit.id"
@@ -226,6 +179,62 @@ export default {
</gl-button-group>
</div>
</div>
+ <div>
+ <div class="d-flex float-left align-items-center align-self-start">
+ <input
+ v-if="isSelectable"
+ class="mr-2"
+ type="checkbox"
+ :checked="checked"
+ @change="$emit('handleCheckboxChange', $event.target.checked)"
+ />
+ <user-avatar-link
+ :link-href="authorUrl"
+ :img-src="authorAvatar"
+ :img-alt="authorName"
+ :img-size="40"
+ class="avatar-cell d-none d-sm-block"
+ />
+ </div>
+ <div class="commit-detail flex-list">
+ <div class="commit-content qa-commit-content">
+ <a
+ :href="commit.commit_url"
+ class="commit-row-message item-title"
+ v-html="commit.title_html"
+ ></a>
+
+ <span class="commit-row-message d-block d-sm-none">&middot; {{ commit.short_id }}</span>
+
+ <gl-button
+ v-if="commit.description_html && collapsible"
+ class="js-toggle-button"
+ size="small"
+ icon="ellipsis_h"
+ :aria-label="__('Toggle commit description')"
+ />
+
+ <div class="committer">
+ <a
+ :href="authorUrl"
+ :class="authorClass"
+ :data-user-id="authorId"
+ v-text="authorName"
+ ></a>
+ {{ s__('CommitWidget|authored') }}
+ <time-ago-tooltip :time="commit.authored_date" />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div>
+ <pre
+ v-if="commit.description_html"
+ :class="{ 'js-toggle-content': collapsible, 'd-block': !collapsible }"
+ class="commit-row-description gl-mb-3 text-dark"
+ v-html="commitDescription"
+ ></pre>
</div>
</li>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
index 73c56514fce..f36fe87ccfa 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
@@ -7,7 +7,6 @@ import CommitMessageField from './message_field.vue';
import Actions from './actions.vue';
import SuccessMessage from './success_message.vue';
import { leftSidebarViews, MAX_WINDOW_HEIGHT_COMPACT } from '../../constants';
-import consts from '../../stores/modules/commit/constants';
import { createUnexpectedCommitError } from '../../lib/errors';
export default {
@@ -45,12 +44,11 @@ export default {
return this.currentActivityView === leftSidebarViews.commit.name;
},
commitErrorPrimaryAction() {
- if (!this.lastCommitError?.canCreateBranch) {
- return undefined;
- }
+ const { primaryAction } = this.lastCommitError || {};
return {
- text: __('Create new branch'),
+ button: primaryAction ? { text: primaryAction.text } : undefined,
+ callback: primaryAction?.callback?.bind(this, this.$store) || (() => {}),
};
},
},
@@ -78,9 +76,6 @@ export default {
commit() {
return this.commitChanges();
},
- forceCreateNewBranch() {
- return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() => this.commit());
- },
handleCompactState() {
if (this.lastCommitMsg) {
this.isCompact = false;
@@ -188,9 +183,9 @@ export default {
ref="commitErrorModal"
modal-id="ide-commit-error-modal"
:title="lastCommitError.title"
- :action-primary="commitErrorPrimaryAction"
+ :action-primary="commitErrorPrimaryAction.button"
:action-cancel="{ text: __('Cancel') }"
- @ok="forceCreateNewBranch"
+ @ok="commitErrorPrimaryAction.callback"
>
<div v-safe-html="lastCommitError.messageHTML"></div>
</gl-modal>
diff --git a/app/assets/javascripts/ide/lib/errors.js b/app/assets/javascripts/ide/lib/errors.js
index 6ae18bc8180..e62d9d1e77f 100644
--- a/app/assets/javascripts/ide/lib/errors.js
+++ b/app/assets/javascripts/ide/lib/errors.js
@@ -1,25 +1,49 @@
import { escape } from 'lodash';
import { __ } from '~/locale';
+import consts from '../stores/modules/commit/constants';
const CODEOWNERS_REGEX = /Push.*protected branches.*CODEOWNERS/;
const BRANCH_CHANGED_REGEX = /changed.*since.*start.*edit/;
+const BRANCH_ALREADY_EXISTS = /branch.*already.*exists/;
-export const createUnexpectedCommitError = () => ({
+const createNewBranchAndCommit = store =>
+ store
+ .dispatch('commit/updateCommitAction', consts.COMMIT_TO_NEW_BRANCH)
+ .then(() => store.dispatch('commit/commitChanges'));
+
+export const createUnexpectedCommitError = message => ({
title: __('Unexpected error'),
- messageHTML: __('Could not commit. An unexpected error occurred.'),
- canCreateBranch: false,
+ messageHTML: escape(message) || __('Could not commit. An unexpected error occurred.'),
});
export const createCodeownersCommitError = message => ({
title: __('CODEOWNERS rule violation'),
messageHTML: escape(message),
- canCreateBranch: true,
+ primaryAction: {
+ text: __('Create new branch'),
+ callback: createNewBranchAndCommit,
+ },
});
export const createBranchChangedCommitError = message => ({
title: __('Branch changed'),
messageHTML: `${escape(message)}<br/><br/>${__('Would you like to create a new branch?')}`,
- canCreateBranch: true,
+ primaryAction: {
+ text: __('Create new branch'),
+ callback: createNewBranchAndCommit,
+ },
+});
+
+export const branchAlreadyExistsCommitError = message => ({
+ title: __('Branch already exists'),
+ messageHTML: `${escape(message)}<br/><br/>${__(
+ 'Would you like to try auto-generating a branch name?',
+ )}`,
+ primaryAction: {
+ text: __('Create new branch'),
+ callback: store =>
+ store.dispatch('commit/addSuffixToBranchName').then(() => createNewBranchAndCommit(store)),
+ },
});
export const parseCommitError = e => {
@@ -33,7 +57,9 @@ export const parseCommitError = e => {
return createCodeownersCommitError(message);
} else if (BRANCH_CHANGED_REGEX.test(message)) {
return createBranchChangedCommitError(message);
+ } else if (BRANCH_ALREADY_EXISTS.test(message)) {
+ return branchAlreadyExistsCommitError(message);
}
- return createUnexpectedCommitError();
+ return createUnexpectedCommitError(message);
};
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
index b8304a9b68d..500ce9f32d5 100644
--- a/app/assets/javascripts/ide/stores/getters.js
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -6,6 +6,7 @@ import {
PERMISSION_CREATE_MR,
PERMISSION_PUSH_CODE,
} from '../constants';
+import { addNumericSuffix } from '~/ide/utils';
import Api from '~/api';
export const activeFile = state => state.openFiles.find(file => file.active) || null;
@@ -167,10 +168,7 @@ export const getAvailableFileName = (state, getters) => path => {
let newPath = path;
while (getters.entryExists(newPath)) {
- newPath = newPath.replace(
- /([ _-]?)(\d*)(\..+?$|$)/,
- (_, before, number, after) => `${before || '_'}${Number(number) + 1}${after}`,
- );
+ newPath = addNumericSuffix(newPath);
}
return newPath;
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index 90a6c644d17..e0d2028d2e1 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -8,6 +8,7 @@ import consts from './constants';
import { leftSidebarViews } from '../../../constants';
import eventHub from '../../../eventhub';
import { parseCommitError } from '../../../lib/errors';
+import { addNumericSuffix } from '~/ide/utils';
export const updateCommitMessage = ({ commit }, message) => {
commit(types.UPDATE_COMMIT_MESSAGE, message);
@@ -17,11 +18,8 @@ export const discardDraft = ({ commit }) => {
commit(types.UPDATE_COMMIT_MESSAGE, '');
};
-export const updateCommitAction = ({ commit, getters }, commitAction) => {
- commit(types.UPDATE_COMMIT_ACTION, {
- commitAction,
- });
- commit(types.TOGGLE_SHOULD_CREATE_MR, !getters.shouldHideNewMrOption);
+export const updateCommitAction = ({ commit }, commitAction) => {
+ commit(types.UPDATE_COMMIT_ACTION, { commitAction });
};
export const toggleShouldCreateMR = ({ commit }) => {
@@ -32,6 +30,12 @@ export const updateBranchName = ({ commit }, branchName) => {
commit(types.UPDATE_NEW_BRANCH_NAME, branchName);
};
+export const addSuffixToBranchName = ({ commit, state }) => {
+ const newBranchName = addNumericSuffix(state.newBranchName, true);
+
+ commit(types.UPDATE_NEW_BRANCH_NAME, newBranchName);
+};
+
export const setLastCommitMessage = ({ commit, rootGetters }, data) => {
const { currentProject } = rootGetters;
const commitStats = data.stats
@@ -107,7 +111,7 @@ export const updateFilesAfterCommit = ({ commit, dispatch, rootState, rootGetter
export const commitChanges = ({ commit, state, getters, dispatch, rootState, rootGetters }) => {
// Pull commit options out because they could change
// During some of the pre and post commit processing
- const { shouldCreateMR, isCreatingNewBranch, branchName } = getters;
+ const { shouldCreateMR, shouldHideNewMrOption, isCreatingNewBranch, branchName } = getters;
const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH;
const stageFilesPromise = rootState.stagedFiles.length
? Promise.resolve()
@@ -167,7 +171,7 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo
commit(rootTypes.SET_LAST_COMMIT_MSG, '', { root: true });
}, 5000);
- if (shouldCreateMR) {
+ if (shouldCreateMR && !shouldHideNewMrOption) {
const { currentProject } = rootGetters;
const targetBranch = isCreatingNewBranch
? rootState.currentBranchId
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutations.js b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
index 2cf6e8e6f36..c4bfad6405e 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
@@ -10,9 +10,7 @@ export default {
Object.assign(state, { commitAction });
},
[types.UPDATE_NEW_BRANCH_NAME](state, newBranchName) {
- Object.assign(state, {
- newBranchName,
- });
+ Object.assign(state, { newBranchName });
},
[types.UPDATE_LOADING](state, submitCommitLoading) {
Object.assign(state, {
diff --git a/app/assets/javascripts/ide/utils.js b/app/assets/javascripts/ide/utils.js
index f7ecf6340d9..404c5c571ba 100644
--- a/app/assets/javascripts/ide/utils.js
+++ b/app/assets/javascripts/ide/utils.js
@@ -139,6 +139,34 @@ export function getFileEOL(content = '') {
return content.includes('\r\n') ? 'CRLF' : 'LF';
}
+/**
+ * Adds or increments the numeric suffix to a filename/branch name.
+ * Retains underscore or dash before the numeric suffix if it already exists.
+ *
+ * Examples:
+ * hello -> hello-1
+ * hello-2425 -> hello-2425
+ * hello.md -> hello-1.md
+ * hello_2.md -> hello_3.md
+ * hello_ -> hello_1
+ * master-patch-22432 -> master-patch-22433
+ * patch_332 -> patch_333
+ *
+ * @param {string} filename File name or branch name
+ * @param {number} [randomize] Should randomize the numeric suffix instead of auto-incrementing?
+ */
+export function addNumericSuffix(filename, randomize = false) {
+ return filename.replace(/([ _-]?)(\d*)(\..+?$|$)/, (_, before, number, after) => {
+ const n = randomize
+ ? Math.random()
+ .toString()
+ .substring(2, 7)
+ .slice(-5)
+ : Number(number) + 1;
+ return `${before || '-'}${n}${after}`;
+ });
+}
+
export const measurePerformance = (
mark,
measureName,
diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue
index 061a5becbed..50f2136325d 100644
--- a/app/assets/javascripts/incidents/components/incidents_list.vue
+++ b/app/assets/javascripts/incidents/components/incidents_list.vue
@@ -16,6 +16,7 @@ import {
GlEmptyState,
} from '@gitlab/ui';
import Api from '~/api';
+import Tracking from '~/tracking';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
@@ -41,6 +42,7 @@ import {
TH_SEVERITY_TEST_ID,
TH_PUBLISHED_TEST_ID,
INCIDENT_DETAILS_PATH,
+ trackIncidentCreateNewOptions,
} from '../constants';
const tdClass =
@@ -58,6 +60,7 @@ const initialPaginationState = {
};
export default {
+ trackIncidentCreateNewOptions,
i18n: I18N,
statusTabs: INCIDENT_STATUS_TABS,
fields: [
@@ -335,6 +338,11 @@ export default {
navigateToIncidentDetails({ iid }) {
return visitUrl(joinPaths(this.issuePath, INCIDENT_DETAILS_PATH, iid));
},
+ navigateToCreateNewIncident() {
+ const { category, action } = this.$options.trackIncidentCreateNewOptions;
+ Tracking.event(category, action);
+ this.redirecting = true;
+ },
handlePageChange(page) {
const { startCursor, endCursor } = this.incidents.pageInfo;
@@ -458,7 +466,7 @@ export default {
category="primary"
variant="success"
:href="newIncidentPath"
- @click="redirecting = true"
+ @click="navigateToCreateNewIncident"
>
{{ $options.i18n.createIncidentBtnLabel }}
</gl-button>
diff --git a/app/assets/javascripts/incidents/constants.js b/app/assets/javascripts/incidents/constants.js
index 797439495e3..bdabf1c3e42 100644
--- a/app/assets/javascripts/incidents/constants.js
+++ b/app/assets/javascripts/incidents/constants.js
@@ -1,3 +1,4 @@
+/* eslint-disable @gitlab/require-i18n-strings */
import { s__, __ } from '~/locale';
export const I18N = {
@@ -34,6 +35,14 @@ export const INCIDENT_STATUS_TABS = [
},
];
+/**
+ * Tracks snowplow event when user clicks create new incident
+ */
+export const trackIncidentCreateNewOptions = {
+ category: 'Incident Management',
+ action: 'create_incident_button_clicks',
+};
+
export const DEFAULT_PAGE_SIZE = 20;
export const TH_CREATED_AT_TEST_ID = { 'data-testid': 'incident-management-created-at-sort' };
export const TH_SEVERITY_TEST_ID = { 'data-testid': 'incident-management-severity-sort' };
diff --git a/app/assets/javascripts/issuable_show/components/issuable_description.vue b/app/assets/javascripts/issuable_show/components/issuable_description.vue
new file mode 100644
index 00000000000..091a4be5bd8
--- /dev/null
+++ b/app/assets/javascripts/issuable_show/components/issuable_description.vue
@@ -0,0 +1,31 @@
+<script>
+import $ from 'jquery';
+import { GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import '~/behaviors/markdown/render_gfm';
+
+export default {
+ directives: {
+ SafeHtml,
+ },
+ props: {
+ issuable: {
+ type: Object,
+ required: true,
+ },
+ },
+ mounted() {
+ this.renderGFM();
+ },
+ methods: {
+ renderGFM() {
+ $(this.$refs.gfmContainer).renderGFM();
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="description">
+ <div ref="gfmContainer" v-safe-html="issuable.descriptionHtml" class="md"></div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issuable_show/components/issuable_edit_form.vue b/app/assets/javascripts/issuable_show/components/issuable_edit_form.vue
new file mode 100644
index 00000000000..7b9a83a740f
--- /dev/null
+++ b/app/assets/javascripts/issuable_show/components/issuable_edit_form.vue
@@ -0,0 +1,135 @@
+<script>
+import $ from 'jquery';
+import { GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui';
+
+import Autosave from '~/autosave';
+import MarkdownField from '~/vue_shared/components/markdown/field.vue';
+
+import eventHub from '../event_hub';
+
+export default {
+ components: {
+ GlForm,
+ GlFormGroup,
+ GlFormInput,
+ MarkdownField,
+ },
+ props: {
+ issuable: {
+ type: Object,
+ required: true,
+ },
+ enableAutocomplete: {
+ type: Boolean,
+ required: true,
+ },
+ descriptionPreviewPath: {
+ type: String,
+ required: true,
+ },
+ descriptionHelpPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ const { title, description } = this.issuable;
+
+ return {
+ title,
+ description,
+ };
+ },
+ created() {
+ eventHub.$on('update.issuable', this.resetAutosave);
+ eventHub.$on('close.form', this.resetAutosave);
+ },
+ mounted() {
+ this.initAutosave();
+ },
+ beforeDestroy() {
+ eventHub.$off('update.issuable', this.resetAutosave);
+ eventHub.$off('close.form', this.resetAutosave);
+ },
+ methods: {
+ initAutosave() {
+ const { titleInput, descriptionInput } = this.$refs;
+
+ if (!titleInput || !descriptionInput) return;
+
+ this.autosaveTitle = new Autosave($(titleInput.$el), [
+ document.location.pathname,
+ document.location.search,
+ 'title',
+ ]);
+
+ this.autosaveDescription = new Autosave($(descriptionInput.$el), [
+ document.location.pathname,
+ document.location.search,
+ 'description',
+ ]);
+ },
+ resetAutosave() {
+ this.autosaveTitle.reset();
+ this.autosaveDescription.reset();
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form>
+ <gl-form-group
+ data-testid="title"
+ :label="__('Title')"
+ :label-sr-only="true"
+ label-for="issuable-title"
+ class="col-12"
+ >
+ <gl-form-input
+ id="issuable-title"
+ ref="titleInput"
+ v-model.trim="title"
+ :placeholder="__('Title')"
+ :aria-label="__('Title')"
+ :autofocus="true"
+ class="qa-title-input"
+ />
+ </gl-form-group>
+ <gl-form-group
+ data-testid="description"
+ :label="__('Description')"
+ :label-sr-only="true"
+ label-for="issuable-description"
+ class="col-12 common-note-form"
+ >
+ <markdown-field
+ :markdown-preview-path="descriptionPreviewPath"
+ :markdown-docs-path="descriptionHelpPath"
+ :enable-autocomplete="enableAutocomplete"
+ :textarea-value="description"
+ >
+ <template #textarea>
+ <textarea
+ id="issuable-description"
+ ref="descriptionInput"
+ v-model="description"
+ :data-supports-quick-actions="enableAutocomplete"
+ :aria-label="__('Description')"
+ :placeholder="__('Write a comment or drag your files here…')"
+ class="note-textarea js-gfm-input js-autosize markdown-area
+ qa-description-textarea"
+ dir="auto"
+ ></textarea>
+ </template>
+ </markdown-field>
+ </gl-form-group>
+ <div data-testid="actions" class="col-12 gl-mt-3 gl-mb-3 clearfix">
+ <slot
+ name="edit-form-actions"
+ :issuable-title="title"
+ :issuable-description="description"
+ ></slot>
+ </div>
+ </gl-form>
+</template>
diff --git a/app/assets/javascripts/issuable_show/components/issuable_title.vue b/app/assets/javascripts/issuable_show/components/issuable_title.vue
new file mode 100644
index 00000000000..d3b42fd2ffb
--- /dev/null
+++ b/app/assets/javascripts/issuable_show/components/issuable_title.vue
@@ -0,0 +1,96 @@
+<script>
+import {
+ GlIcon,
+ GlButton,
+ GlIntersectionObserver,
+ GlTooltipDirective,
+ GlSafeHtmlDirective as SafeHtml,
+} from '@gitlab/ui';
+
+export default {
+ components: {
+ GlIcon,
+ GlButton,
+ GlIntersectionObserver,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ SafeHtml,
+ },
+ props: {
+ issuable: {
+ type: Object,
+ required: true,
+ },
+ statusBadgeClass: {
+ type: String,
+ required: true,
+ },
+ statusIcon: {
+ type: String,
+ required: true,
+ },
+ enableEdit: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ stickyTitleVisible: false,
+ };
+ },
+ methods: {
+ handleTitleAppear() {
+ this.stickyTitleVisible = false;
+ },
+ handleTitleDisappear() {
+ this.stickyTitleVisible = true;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="title-container">
+ <h2 v-safe-html="issuable.titleHtml" class="title qa-title" dir="auto"></h2>
+ <gl-button
+ v-if="enableEdit"
+ v-gl-tooltip.bottom
+ :title="__('Edit title and description')"
+ icon="pencil"
+ class="btn-edit js-issuable-edit qa-edit-button"
+ @click="$emit('edit-issuable', $event)"
+ />
+ </div>
+ <gl-intersection-observer @appear="handleTitleAppear" @disappear="handleTitleDisappear">
+ <transition name="issuable-header-slide">
+ <div
+ v-if="stickyTitleVisible"
+ class="issue-sticky-header gl-fixed gl-z-index-3 gl-bg-white gl-border-1 gl-border-b-solid gl-border-b-gray-100 gl-py-3"
+ data-testid="header"
+ >
+ <div
+ class="issue-sticky-header-text gl-display-flex gl-align-items-center gl-mx-auto gl-px-5"
+ >
+ <p
+ data-testid="status"
+ class="issuable-status-box status-box gl-my-0"
+ :class="statusBadgeClass"
+ >
+ <gl-icon :name="statusIcon" class="gl-display-block d-sm-none gl-h-6!" />
+ <span class="gl-display-none d-sm-block"><slot name="status-badge"></slot></span>
+ </p>
+ <p
+ class="gl-font-weight-bold gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis gl-my-0"
+ :title="issuable.title"
+ >
+ {{ issuable.title }}
+ </p>
+ </div>
+ </div>
+ </transition>
+ </gl-intersection-observer>
+ </div>
+</template>
diff --git a/app/assets/javascripts/issuable_show/event_hub.js b/app/assets/javascripts/issuable_show/event_hub.js
new file mode 100644
index 00000000000..e31806ad199
--- /dev/null
+++ b/app/assets/javascripts/issuable_show/event_hub.js
@@ -0,0 +1,3 @@
+import createEventHub from '~/helpers/event_hub_factory';
+
+export default createEventHub();
diff --git a/app/assets/javascripts/packages/details/components/composer_installation.vue b/app/assets/javascripts/packages/details/components/composer_installation.vue
index 60ad468c293..0518fac98fc 100644
--- a/app/assets/javascripts/packages/details/components/composer_installation.vue
+++ b/app/assets/javascripts/packages/details/components/composer_installation.vue
@@ -14,12 +14,12 @@ export default {
},
computed: {
...mapState(['composerHelpPath']),
- ...mapGetters(['composerRegistryInclude', 'composerPackageInclude']),
+ ...mapGetters(['composerRegistryInclude', 'composerPackageInclude', 'groupExists']),
},
i18n: {
- registryInclude: s__('PackageRegistry|composer.json registry include'),
+ registryInclude: s__('PackageRegistry|Add composer registry'),
copyRegistryInclude: s__('PackageRegistry|Copy registry include'),
- packageInclude: s__('PackageRegistry|composer.json require package include'),
+ packageInclude: s__('PackageRegistry|Install package version'),
copyPackageInclude: s__('PackageRegistry|Copy require package include'),
infoLine: s__(
'PackageRegistry|For more information on Composer packages in GitLab, %{linkStart}see the documentation.%{linkEnd}',
@@ -32,31 +32,33 @@ export default {
<template>
<div>
- <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
+ <div v-if="groupExists">
+ <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
- <code-instruction
- :label="$options.i18n.registryInclude"
- :instruction="composerRegistryInclude"
- :copy-text="$options.i18n.copyRegistryInclude"
- :tracking-action="$options.trackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- data-testid="registry-include"
- />
+ <code-instruction
+ :label="$options.i18n.registryInclude"
+ :instruction="composerRegistryInclude"
+ :copy-text="$options.i18n.copyRegistryInclude"
+ :tracking-action="$options.trackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND"
+ :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
+ data-testid="registry-include"
+ />
- <code-instruction
- :label="$options.i18n.packageInclude"
- :instruction="composerPackageInclude"
- :copy-text="$options.i18n.copyPackageInclude"
- :tracking-action="$options.trackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- data-testid="package-include"
- />
- <span data-testid="help-text">
- <gl-sprintf :message="$options.i18n.infoLine">
- <template #link="{ content }">
- <gl-link :href="composerHelpPath" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </span>
+ <code-instruction
+ :label="$options.i18n.packageInclude"
+ :instruction="composerPackageInclude"
+ :copy-text="$options.i18n.copyPackageInclude"
+ :tracking-action="$options.trackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND"
+ :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
+ data-testid="package-include"
+ />
+ <span data-testid="help-text">
+ <gl-sprintf :message="$options.i18n.infoLine">
+ <template #link="{ content }">
+ <gl-link :href="composerHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </span>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/packages/details/store/getters.js b/app/assets/javascripts/packages/details/store/getters.js
index bb0ae3e9ab7..14e76ac84bd 100644
--- a/app/assets/javascripts/packages/details/store/getters.js
+++ b/app/assets/javascripts/packages/details/store/getters.js
@@ -102,11 +102,12 @@ repository = ${pypiSetupPath}
username = __token__
password = <your personal access token>`;
-export const composerRegistryInclude = ({ composerPath }) => {
- const base = { type: 'composer', url: composerPath };
- return JSON.stringify(base);
-};
-export const composerPackageInclude = ({ packageEntity }) => {
- const base = { [packageEntity.name]: packageEntity.version };
- return JSON.stringify(base);
-};
+export const composerRegistryInclude = ({ composerPath, composerConfigRepositoryName }) =>
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ `composer config repositories.${composerConfigRepositoryName} '{"type": "composer", "url": "${composerPath}"}'`;
+
+export const composerPackageInclude = ({ packageEntity }) =>
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ `composer req ${[packageEntity.name]}:${packageEntity.version}`;
+
+export const groupExists = ({ groupListUrl }) => groupListUrl.length > 0;
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
index 089cac9ee4c..e18cfefc3ca 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
@@ -1,16 +1,12 @@
<script>
import { GlButton, GlFormSelect, GlToggle, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
-import tooltip from '~/vue_shared/directives/tooltip';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import eventHub from '../event_hub';
export default {
name: 'ServiceDeskSetting',
- directives: {
- tooltip,
- },
components: {
ClipboardButton,
GlButton,
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 3e87833f7f5..0e2bccfabdd 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -2,6 +2,7 @@
/* eslint-disable vue/no-v-html */
import { GlTooltipDirective, GlLink, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
import defaultAvatarUrl from 'images/no_avatar.png';
+import pathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql';
import { sprintf, s__ } from '~/locale';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
@@ -9,7 +10,6 @@ import CiIcon from '../../vue_shared/components/ci_icon.vue';
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql';
-import pathLastCommitQuery from '../queries/path_last_commit.query.graphql';
export default {
components: {
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 65da8f70b40..0e4d724e949 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import PathLastCommitQuery from 'shared_queries/repository/path_last_commit.query.graphql';
import { escapeFileUrl, joinPaths, webIDEUrl } from '../lib/utils/url_utility';
import createRouter from './router';
import App from './components/app.vue';
@@ -18,6 +19,10 @@ export default function setupVueRepositoryList() {
const { dataset } = el;
const { projectPath, projectShortPath, ref, escapedRef, fullName } = dataset;
const router = createRouter(projectPath, escapedRef);
+ const pathRegex = /-\/tree\/[^/]+\/(.+$)/;
+ const matches = window.location.href.match(pathRegex);
+
+ const currentRoutePath = matches ? matches[1] : '';
apolloProvider.clients.defaultClient.cache.writeData({
data: {
@@ -29,6 +34,43 @@ export default function setupVueRepositoryList() {
},
});
+ const initLastCommitApp = () =>
+ new Vue({
+ el: document.getElementById('js-last-commit'),
+ router,
+ apolloProvider,
+ render(h) {
+ return h(LastCommit, {
+ props: {
+ currentPath: this.$route.params.path,
+ },
+ });
+ },
+ });
+
+ if (window.gl.startup_graphql_calls) {
+ const query = window.gl.startup_graphql_calls.find(
+ call => call.operationName === 'pathLastCommit',
+ );
+ query.fetchCall
+ .then(res => res.json())
+ .then(res => {
+ apolloProvider.clients.defaultClient.writeQuery({
+ query: PathLastCommitQuery,
+ data: res.data,
+ variables: {
+ projectPath,
+ ref,
+ path: currentRoutePath,
+ },
+ });
+ })
+ .catch(() => {})
+ .finally(() => initLastCommitApp());
+ } else {
+ initLastCommitApp();
+ }
+
router.afterEach(({ params: { path } }) => {
setTitle(path, ref, fullName);
});
@@ -77,20 +119,6 @@ export default function setupVueRepositoryList() {
});
}
- // eslint-disable-next-line no-new
- new Vue({
- el: document.getElementById('js-last-commit'),
- router,
- apolloProvider,
- render(h) {
- return h(LastCommit, {
- props: {
- currentPath: this.$route.params.path,
- },
- });
- },
- });
-
const treeHistoryLinkEl = document.getElementById('js-tree-history-link');
const { historyLink } = treeHistoryLinkEl.dataset;
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index acf6f65b1a0..46749fc5e87 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -4,6 +4,7 @@ import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_
import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_widget_service';
import MrWidgetApprovals from 'ee_else_ce/vue_merge_request_widget/components/approvals/approvals.vue';
import stateMaps from 'ee_else_ce/vue_merge_request_widget/stores/state_maps';
+import { GlSafeHtmlDirective } from '@gitlab/ui';
import { sprintf, s__, __ } from '~/locale';
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
@@ -52,6 +53,9 @@ export default {
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
// eslint-disable-next-line @gitlab/require-i18n-strings
name: 'MRWidget',
+ directives: {
+ SafeHtml: GlSafeHtmlDirective,
+ },
components: {
Loading,
'mr-widget-header': WidgetHeader,
@@ -510,7 +514,7 @@ export default {
</mr-widget-alert-message>
<mr-widget-alert-message v-if="mr.mergeError" type="danger">
- {{ mergeError }}
+ <span v-safe-html="mergeError"></span>
</mr-widget-alert-message>
<source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
diff --git a/app/assets/javascripts/vue_shared/components/split_button.vue b/app/assets/javascripts/vue_shared/components/split_button.vue
index e9b99c6ea78..11049028ff6 100644
--- a/app/assets/javascripts/vue_shared/components/split_button.vue
+++ b/app/assets/javascripts/vue_shared/components/split_button.vue
@@ -1,19 +1,15 @@
<script>
import { isString } from 'lodash';
-import {
- GlDeprecatedDropdown,
- GlDeprecatedDropdownDivider,
- GlDeprecatedDropdownItem,
-} from '@gitlab/ui';
+import { GlDropdown, GlDropdownDivider, GlDropdownItem } from '@gitlab/ui';
const isValidItem = item =>
isString(item.eventName) && isString(item.title) && isString(item.description);
export default {
components: {
- GlDeprecatedDropdown,
- GlDeprecatedDropdownDivider,
- GlDeprecatedDropdownItem,
+ GlDropdown,
+ GlDropdownDivider,
+ GlDropdownItem,
},
props: {
@@ -32,7 +28,7 @@ export default {
variant: {
type: String,
required: false,
- default: 'secondary',
+ default: 'default',
},
},
@@ -61,8 +57,8 @@ export default {
</script>
<template>
- <gl-deprecated-dropdown
- :menu-class="`dropdown-menu-selectable ${menuClass}`"
+ <gl-dropdown
+ :menu-class="menuClass"
split
:text="dropdownToggleText"
:variant="variant"
@@ -70,20 +66,20 @@ export default {
@click="triggerEvent"
>
<template v-for="(item, itemIndex) in actionItems">
- <gl-deprecated-dropdown-item
+ <gl-dropdown-item
:key="item.eventName"
- :active="selectedItem === item"
- active-class="is-active"
+ :is-check-item="true"
+ :is-checked="selectedItem === item"
@click="changeSelectedItem(item)"
>
<strong>{{ item.title }}</strong>
<div>{{ item.description }}</div>
- </gl-deprecated-dropdown-item>
+ </gl-dropdown-item>
- <gl-deprecated-dropdown-divider
+ <gl-dropdown-divider
v-if="itemIndex < actionItems.length - 1"
:key="`${item.eventName}-divider`"
/>
</template>
- </gl-deprecated-dropdown>
+ </gl-dropdown>
</template>
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index d3ab4be925b..d49134eb648 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -10,8 +10,6 @@
@import './pages/detail_page';
@import './pages/editor';
@import './pages/environment_logs';
-@import './pages/error_list';
-@import './pages/error_tracking_list';
@import './pages/events';
@import './pages/experience_level';
@import './pages/experimental_separate_sign_up';
diff --git a/app/assets/stylesheets/pages/error_list.scss b/app/assets/stylesheets/page_bundles/error_tracking_index.scss
index 3ec3e4f6b43..65bddfb7890 100644
--- a/app/assets/stylesheets/pages/error_list.scss
+++ b/app/assets/stylesheets/page_bundles/error_tracking_index.scss
@@ -1,4 +1,10 @@
+@import 'page_bundles/mixins_and_variables_and_functions';
+
.error-list {
+ .dropdown {
+ min-width: auto;
+ }
+
.sort-control {
.btn {
padding-right: 2rem;
@@ -17,7 +23,7 @@
min-height: 68px;
&:last-child {
- background-color: $gray-10;
+ background-color: var(--gray-10, $gray-10);
&::before {
content: none !important;
diff --git a/app/assets/stylesheets/page_bundles/merge_conflicts.scss b/app/assets/stylesheets/page_bundles/merge_conflicts.scss
index 25d913c79de..b0655408edf 100644
--- a/app/assets/stylesheets/page_bundles/merge_conflicts.scss
+++ b/app/assets/stylesheets/page_bundles/merge_conflicts.scss
@@ -226,6 +226,14 @@ $colors: (
.solarized-dark {
@include color-scheme('solarized-dark'); }
+ .none {
+ .line_content.header {
+ button {
+ color: $gray-900;
+ }
+ }
+ }
+
.diff-wrap-lines .line_content {
white-space: normal;
min-height: 19px;
diff --git a/app/assets/stylesheets/pages/error_tracking_list.scss b/app/assets/stylesheets/pages/error_tracking_list.scss
deleted file mode 100644
index cc391ca6c97..00000000000
--- a/app/assets/stylesheets/pages/error_tracking_list.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-.error-list {
- .dropdown {
- min-width: auto;
- }
-}
diff --git a/app/assets/javascripts/repository/queries/path_last_commit.query.graphql b/app/graphql/queries/repository/path_last_commit.query.graphql
index 51f3f790a5d..d845f7c6224 100644
--- a/app/assets/javascripts/repository/queries/path_last_commit.query.graphql
+++ b/app/graphql/queries/repository/path_last_commit.query.graphql
@@ -1,8 +1,12 @@
query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
project(fullPath: $projectPath) {
+ __typename
repository {
+ __typename
tree(path: $path, ref: $ref) {
+ __typename
lastCommit {
+ __typename
sha
title
titleHtml
@@ -13,15 +17,20 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
authorName
authorGravatar
author {
+ __typename
name
avatarUrl
webPath
}
signatureHtml
pipelines(ref: $ref, first: 1) {
+ __typename
edges {
+ __typename
node {
+ __typename
detailedStatus {
+ __typename
detailsPath
icon
tooltip
diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb
index a3a899dbe1a..8f365fd0786 100644
--- a/app/helpers/packages_helper.rb
+++ b/app/helpers/packages_helper.rb
@@ -34,6 +34,10 @@ module PackagesHelper
expose_url(api_v4_group___packages_composer_packages_path(id: group_id, format: '.json'))
end
+ def composer_config_repository_name(group_id)
+ "#{Gitlab.config.gitlab.host}/#{group_id}"
+ end
+
def packages_list_data(type, resource)
{
resource_id: resource.id,
diff --git a/app/helpers/startupjs_helper.rb b/app/helpers/startupjs_helper.rb
new file mode 100644
index 00000000000..b595590c7c9
--- /dev/null
+++ b/app/helpers/startupjs_helper.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module StartupjsHelper
+ def page_startup_graphql_calls
+ @graphql_startup_calls
+ end
+
+ def add_page_startup_graphql_call(query, variables = {})
+ @graphql_startup_calls ||= []
+ file_location = File.join(Rails.root, "app/graphql/queries/#{query}.query.graphql")
+
+ return unless File.exist?(file_location)
+
+ query_str = File.read(file_location)
+ @graphql_startup_calls << { query: query_str, variables: variables }
+ end
+end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 5fe1b451ccd..83400c9e533 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -74,8 +74,8 @@ class Commit
sha[0..MIN_SHA_LENGTH]
end
- def diff_safe_lines
- Gitlab::Git::DiffCollection.default_limits[:max_lines]
+ def diff_safe_lines(project: nil)
+ Gitlab::Git::DiffCollection.default_limits(project: project)[:max_lines]
end
def diff_hard_limit_files(project: nil)
diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb
index 610288c5e76..088e6f031c8 100644
--- a/app/services/members/create_service.rb
+++ b/app/services/members/create_service.rb
@@ -7,7 +7,7 @@ module Members
def execute(source)
return error(s_('AddMember|No users specified.')) if params[:user_ids].blank?
- user_ids = params[:user_ids].split(',').uniq
+ user_ids = params[:user_ids].split(',').uniq.flatten
return error(s_("AddMember|Too many users specified (limit is %{user_limit})") % { user_limit: user_limit }) if
user_limit && user_ids.size > user_limit
diff --git a/app/views/admin/dev_ops_report/show.html.haml b/app/views/admin/dev_ops_report/show.html.haml
index f8dadaaa175..88105be70fb 100644
--- a/app/views/admin/dev_ops_report/show.html.haml
+++ b/app/views/admin/dev_ops_report/show.html.haml
@@ -26,7 +26,7 @@
- @metric.cards.each do |card|
= render 'card', card: card
- .devops-steps.d-none.d-lg-block.d-xl-block
+ .devops-steps.d-none.d-lg-block
- @metric.idea_to_production_steps.each_with_index do |step, index|
.devops-step{ class: "devops-#{score_level(step.percentage_score)}-score" }
= custom_icon("i2p_step_#{index + 1}")
diff --git a/app/views/layouts/_startup_js.html.haml b/app/views/layouts/_startup_js.html.haml
index 33c759b7a7c..f312e00c394 100644
--- a/app/views/layouts/_startup_js.html.haml
+++ b/app/views/layouts/_startup_js.html.haml
@@ -1,9 +1,11 @@
-- return unless page_startup_api_calls.present?
+- return unless page_startup_api_calls.present? || page_startup_graphql_calls.present?
= javascript_tag nonce: true do
:plain
var gl = window.gl || {};
gl.startup_calls = #{page_startup_api_calls.to_json};
+ gl.startup_graphql_calls = #{page_startup_graphql_calls.to_json};
+
if (gl.startup_calls && window.fetch) {
Object.keys(gl.startup_calls).forEach(apiCall => {
// fetch won’t send cookies in older browsers, unless you set the credentials init option.
@@ -14,3 +16,21 @@
};
});
}
+ if (gl.startup_graphql_calls && window.fetch) {
+ const url = `#{api_graphql_url}`
+
+ const opts = {
+ method: "POST",
+ headers: { "Content-Type": "application/json", 'X-CSRF-Token': "#{form_authenticity_token}" },
+ };
+
+ gl.startup_graphql_calls = gl.startup_graphql_calls.map(call => ({
+ operationName: call.query.match(/^query (.+)\(/)[1],
+ fetchCall: fetch(url, {
+ ...opts,
+ credentials: 'same-origin',
+ body: JSON.stringify(call)
+ })
+ }))
+ }
+
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index a945ff5aedf..5a7830e306a 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -1,4 +1,4 @@
-- too_big = diff_file.diff_lines.count > Commit.diff_safe_lines
+- too_big = diff_file.diff_lines.count > Commit.diff_safe_lines(project: @project)
- if too_big
.suppressed-container
%a.show-suppressed-diff.cursor-pointer.js-show-suppressed-diff= _("Changes suppressed. Click to show.")
diff --git a/app/views/projects/error_tracking/index.html.haml b/app/views/projects/error_tracking/index.html.haml
index 96f61584a99..ffe0785d327 100644
--- a/app/views/projects/error_tracking/index.html.haml
+++ b/app/views/projects/error_tracking/index.html.haml
@@ -1,3 +1,4 @@
- page_title _('Errors')
+- add_page_specific_style 'page_bundles/error_tracking_index'
#js-error_tracking{ data: error_tracking_data(@current_user, @project) }
diff --git a/app/views/projects/packages/packages/show.html.haml b/app/views/projects/packages/packages/show.html.haml
index 97a3c6e7092..aeca3f5b3e3 100644
--- a/app/views/projects/packages/packages/show.html.haml
+++ b/app/views/projects/packages/packages/show.html.haml
@@ -24,4 +24,5 @@
composer_help_path: help_page_path('user/packages/composer_repository/index'),
project_name: @project.name,
project_list_url: project_packages_path(@project),
- group_list_url: @project.group ? group_packages_path(@project.group) : ''} }
+ group_list_url: @project.group ? group_packages_path(@project.group) : '',
+ composer_config_repository_name: composer_config_repository_name(@project.group&.id)} }
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 3dd12a7b641..8c874ed1505 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -1,3 +1,5 @@
+- current_route_path = request.fullpath.match(/-\/tree\/[^\/]+\/(.+$)/).to_a[1]
+- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, currentRoutePath: current_route_path })
- breadcrumb_title _("Repository")
- @content_class = "limit-container-width" unless fluid_layout