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:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/content_editor/services/upload_file.js44
-rw-r--r--app/assets/javascripts/groups/groups_filterable_list.js4
-rw-r--r--app/assets/javascripts/issuable/components/issuable_by_email.vue2
-rw-r--r--app/assets/javascripts/issues_list/components/issue_card_time_info.vue2
-rw-r--r--app/assets/javascripts/issues_list/components/issues_list_app.vue8
-rw-r--r--app/assets/javascripts/issues_list/constants.js2
-rw-r--r--app/assets/javascripts/issues_list/index.js2
-rw-r--r--app/assets/javascripts/jobs/components/empty_state.vue11
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue6
-rw-r--r--app/assets/javascripts/jobs/components/manual_variables_form.vue39
-rw-r--r--app/assets/javascripts/jobs/index.js2
-rw-r--r--app/assets/javascripts/lib/graphql.js52
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js32
-rw-r--r--app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue6
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue1
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/update_commit_sha.mutation.graphql3
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/latest_commit_sha.query.graphql12
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/resolvers.js13
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue35
-rw-r--r--app/assets/javascripts/profile/preferences/components/profile_preferences.vue3
-rw-r--r--app/assets/javascripts/repository/components/tree_content.vue20
-rw-r--r--app/assets/javascripts/repository/constants.js3
-rw-r--r--app/assets/stylesheets/utilities.scss10
-rw-r--r--app/graphql/resolvers/ci/template_resolver.rb2
-rw-r--r--app/helpers/ci/jobs_helper.rb1
-rw-r--r--app/helpers/ci/pipeline_editor_helper.rb8
-rw-r--r--app/models/award_emoji.rb11
-rw-r--r--app/models/error_tracking/error_event.rb2
-rw-r--r--app/validators/json_schemas/error_tracking_event_payload.json111
29 files changed, 259 insertions, 188 deletions
diff --git a/app/assets/javascripts/content_editor/services/upload_file.js b/app/assets/javascripts/content_editor/services/upload_file.js
new file mode 100644
index 00000000000..613c53144a1
--- /dev/null
+++ b/app/assets/javascripts/content_editor/services/upload_file.js
@@ -0,0 +1,44 @@
+import axios from '~/lib/utils/axios_utils';
+
+const extractAttachmentLinkUrl = (html) => {
+ const parser = new DOMParser();
+ const { body } = parser.parseFromString(html, 'text/html');
+ const link = body.querySelector('a');
+ const src = link.getAttribute('href');
+ const { canonicalSrc } = link.dataset;
+
+ return { src, canonicalSrc };
+};
+
+/**
+ * Uploads a file with a post request to the URL indicated
+ * in the uploadsPath parameter. The expected response of the
+ * uploads service is a JSON object that contains, at least, a
+ * link property. The link property should contain markdown link
+ * definition (i.e. [GitLab](https://gitlab.com)).
+ *
+ * This Markdown will be rendered to extract its canonical and full
+ * URLs using GitLab Flavored Markdown renderer in the backend.
+ *
+ * @param {Object} params
+ * @param {String} params.uploadsPath An absolute URL that points to a service
+ * that allows sending a file for uploading via POST request.
+ * @param {String} params.renderMarkdown A function that accepts a markdown string
+ * and returns a rendered version in HTML format.
+ * @param {File} params.file The file to upload
+ *
+ * @returns Returns an object with two properties:
+ *
+ * canonicalSrc: The URL as defined in the Markdown
+ * src: The absolute URL that points to the resource in the server
+ */
+export const uploadFile = async ({ uploadsPath, renderMarkdown, file }) => {
+ const formData = new FormData();
+ formData.append('file', file, file.name);
+
+ const { data } = await axios.post(uploadsPath, formData);
+ const { markdown } = data.link;
+ const rendered = await renderMarkdown(markdown);
+
+ return extractAttachmentLinkUrl(rendered);
+};
diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js
index 8a66d4d11b7..a7d44322eb1 100644
--- a/app/assets/javascripts/groups/groups_filterable_list.js
+++ b/app/assets/javascripts/groups/groups_filterable_list.js
@@ -86,11 +86,11 @@ export default class GroupFilterableList extends FilterableList {
// Get option query param, also preserve currently applied query param
const sortParam = getParameterByName(
'sort',
- isOptionFilterBySort ? e.currentTarget.href : window.location.href,
+ isOptionFilterBySort ? e.currentTarget.search : window.location.search,
);
const archivedParam = getParameterByName(
'archived',
- isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href,
+ isOptionFilterByArchivedProjects ? e.currentTarget.search : window.location.search,
);
if (sortParam) {
diff --git a/app/assets/javascripts/issuable/components/issuable_by_email.vue b/app/assets/javascripts/issuable/components/issuable_by_email.vue
index 48eadec1d5c..6e300831e00 100644
--- a/app/assets/javascripts/issuable/components/issuable_by_email.vue
+++ b/app/assets/javascripts/issuable/components/issuable_by_email.vue
@@ -36,7 +36,7 @@ export default {
default: null,
},
issuableType: {
- default: '',
+ default: 'issue',
},
emailsHelpPagePath: {
default: '',
diff --git a/app/assets/javascripts/issues_list/components/issue_card_time_info.vue b/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
index 70d73aca925..07492b0046c 100644
--- a/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
+++ b/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
@@ -115,7 +115,7 @@ export default {
{{ timeEstimate }}
</span>
<weight-count
- class="gl-display-none gl-sm-display-inline-block gl-mr-3"
+ class="issuable-weight gl-display-none gl-sm-display-inline-block gl-mr-3"
:weight="issue.weight"
/>
<issue-health-status
diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue
index fcd31013fe5..e3cc43d2679 100644
--- a/app/assets/javascripts/issues_list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue
@@ -664,7 +664,7 @@ export default {
v-gl-tooltip
class="gl-display-none gl-sm-display-block"
:title="$options.i18n.relatedMergeRequests"
- data-testid="issuable-mr"
+ data-testid="merge-requests"
>
<gl-icon name="merge-request" />
{{ issuable.mergeRequestsCount }}
@@ -672,7 +672,7 @@ export default {
<li
v-if="issuable.upvotes"
v-gl-tooltip
- class="gl-display-none gl-sm-display-block"
+ class="issuable-upvotes gl-display-none gl-sm-display-block"
:title="$options.i18n.upvotes"
data-testid="issuable-upvotes"
>
@@ -682,7 +682,7 @@ export default {
<li
v-if="issuable.downvotes"
v-gl-tooltip
- class="gl-display-none gl-sm-display-block"
+ class="issuable-downvotes gl-display-none gl-sm-display-block"
:title="$options.i18n.downvotes"
data-testid="issuable-downvotes"
>
@@ -690,7 +690,7 @@ export default {
{{ issuable.downvotes }}
</li>
<blocking-issues-count
- class="gl-display-none gl-sm-display-block"
+ class="blocking-issues gl-display-none gl-sm-display-block"
:blocking-issues-count="issuable.blockedByCount"
:is-list-item="true"
/>
diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js
index e4b9136343e..d94d4b9a19a 100644
--- a/app/assets/javascripts/issues_list/constants.js
+++ b/app/assets/javascripts/issues_list/constants.js
@@ -97,7 +97,7 @@ export const i18n = {
relatedMergeRequests: __('Related merge requests'),
reorderError: __('An error occurred while reordering issues.'),
rssLabel: __('Subscribe to RSS feed'),
- searchPlaceholder: __('Search or filter results…'),
+ searchPlaceholder: __('Search or filter results...'),
upvotes: __('Upvotes'),
};
diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js
index dc73d8c7cc8..71ceb9bef55 100644
--- a/app/assets/javascripts/issues_list/index.js
+++ b/app/assets/javascripts/issues_list/index.js
@@ -1,6 +1,5 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { IssuableType } from '~/issue_show/constants';
import IssuesListApp from '~/issues_list/components/issues_list_app.vue';
import createDefaultClient from '~/lib/graphql';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
@@ -150,7 +149,6 @@ export function mountIssuesListApp() {
// For IssuableByEmail component
emailsHelpPagePath,
initialEmail,
- issuableType: IssuableType.Issue,
markdownHelpPath,
quickActionsHelpPath,
resetPath,
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
index 35b16d73cc7..e31c13f40b0 100644
--- a/app/assets/javascripts/jobs/components/empty_state.vue
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -35,11 +35,6 @@ export default {
required: false,
default: false,
},
- variablesSettingsUrl: {
- type: String,
- required: false,
- default: null,
- },
action: {
type: Object,
required: false,
@@ -75,11 +70,7 @@ export default {
<p v-if="content" data-testid="job-empty-state-content">{{ content }}</p>
</div>
- <manual-variables-form
- v-if="shouldRenderManualVariables"
- :action="action"
- :variables-settings-url="variablesSettingsUrl"
- />
+ <manual-variables-form v-if="shouldRenderManualVariables" :action="action" />
<div class="text-content">
<div v-if="action && !shouldRenderManualVariables" class="text-center">
<gl-link
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index be95001a396..fa9ee56c049 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -50,11 +50,6 @@ export default {
required: false,
default: null,
},
- variablesSettingsUrl: {
- type: String,
- required: false,
- default: null,
- },
deploymentHelpUrl: {
type: String,
required: false,
@@ -315,7 +310,6 @@ export default {
:action="emptyStateAction"
:playable="job.playable"
:scheduled="job.scheduled"
- :variables-settings-url="variablesSettingsUrl"
/>
<!-- EO empty state -->
diff --git a/app/assets/javascripts/jobs/components/manual_variables_form.vue b/app/assets/javascripts/jobs/components/manual_variables_form.vue
index d45012d2023..269551ff9aa 100644
--- a/app/assets/javascripts/jobs/components/manual_variables_form.vue
+++ b/app/assets/javascripts/jobs/components/manual_variables_form.vue
@@ -1,14 +1,16 @@
<script>
-/* eslint-disable vue/no-v-html */
-import { GlButton } from '@gitlab/ui';
+import { GlButton, GlLink, GlSprintf } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { mapActions } from 'vuex';
-import { s__, sprintf } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { s__ } from '~/locale';
export default {
name: 'ManualVariablesForm',
components: {
GlButton,
+ GlLink,
+ GlSprintf,
},
props: {
action: {
@@ -24,11 +26,6 @@ export default {
);
},
},
- variablesSettingsUrl: {
- type: String,
- required: true,
- default: '',
- },
},
inputTypes: {
key: 'key',
@@ -37,6 +34,9 @@ export default {
i18n: {
keyPlaceholder: s__('CiVariables|Input variable key'),
valuePlaceholder: s__('CiVariables|Input variable value'),
+ formHelpText: s__(
+ 'CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default',
+ ),
},
data() {
return {
@@ -47,17 +47,8 @@ export default {
};
},
computed: {
- helpText() {
- return sprintf(
- s__(
- 'CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default',
- ),
- {
- linkStart: `<a href="${this.variablesSettingsUrl}">`,
- linkEnd: '</a>',
- },
- false,
- );
+ variableSettings() {
+ return helpPagePath('ci/variables/index', { anchor: 'add-a-cicd-variable-to-a-project' });
},
},
watch: {
@@ -188,8 +179,14 @@ export default {
</div>
</div>
</div>
- <div class="d-flex gl-mt-3 justify-content-center">
- <p class="text-muted" data-testid="form-help-text" v-html="helpText"></p>
+ <div class="gl-text-center gl-mt-3">
+ <gl-sprintf :message="$options.i18n.formHelpText">
+ <template #link="{ content }">
+ <gl-link :href="variableSettings" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
</div>
<div class="d-flex justify-content-center">
<gl-button
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
index 260190f5043..1fb6a6f9850 100644
--- a/app/assets/javascripts/jobs/index.js
+++ b/app/assets/javascripts/jobs/index.js
@@ -15,7 +15,6 @@ export default () => {
deploymentHelpUrl,
codeQualityHelpUrl,
runnerSettingsUrl,
- variablesSettingsUrl,
subscriptionsMoreMinutesUrl,
endpoint,
pagePath,
@@ -41,7 +40,6 @@ export default () => {
deploymentHelpUrl,
codeQualityHelpUrl,
runnerSettingsUrl,
- variablesSettingsUrl,
subscriptionsMoreMinutesUrl,
endpoint,
pagePath,
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index cec689a44ca..d91fc61ba21 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -2,12 +2,13 @@ import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';
import { ApolloLink } from 'apollo-link';
import { BatchHttpLink } from 'apollo-link-batch-http';
-import { createHttpLink } from 'apollo-link-http';
+import { HttpLink } from 'apollo-link-http';
import { createUploadLink } from 'apollo-upload-client';
import ActionCableLink from '~/actioncable_link';
import { apolloCaptchaLink } from '~/captcha/apollo_captcha_link';
import { StartupJSLink } from '~/lib/utils/apollo_startup_js_link';
import csrf from '~/lib/utils/csrf';
+import { objectToQuery, queryToObject } from '~/lib/utils/url_utility';
import PerformanceBarService from '~/performance_bar/services/performance_bar_service';
export const fetchPolicies = {
@@ -18,6 +19,31 @@ export const fetchPolicies = {
CACHE_ONLY: 'cache-only',
};
+export const stripWhitespaceFromQuery = (url, path) => {
+ /* eslint-disable-next-line no-unused-vars */
+ const [_, params] = url.split(path);
+
+ if (!params) {
+ return url;
+ }
+
+ const decoded = decodeURIComponent(params);
+ const paramsObj = queryToObject(decoded);
+
+ if (!paramsObj.query) {
+ return url;
+ }
+
+ const stripped = paramsObj.query
+ .split(/\s+|\n/)
+ .join(' ')
+ .trim();
+ paramsObj.query = stripped;
+
+ const reassembled = objectToQuery(paramsObj);
+ return `${path}?${reassembled}`;
+};
+
export default (resolvers = {}, config = {}) => {
const {
assumeImmutableResults,
@@ -58,10 +84,31 @@ export default (resolvers = {}, config = {}) => {
});
});
+ /*
+ This custom fetcher intervention is to deal with an issue where we are using GET to access
+ eTag polling, but Apollo Client adds excessive whitespace, which causes the
+ request to fail on certain self-hosted stacks. When we can move
+ to subscriptions entirely or can land an upstream PR, this can be removed.
+
+ Related links
+ Bug report: https://gitlab.com/gitlab-org/gitlab/-/issues/329895
+ Moving to subscriptions: https://gitlab.com/gitlab-org/gitlab/-/issues/332485
+ Apollo Client issue: https://github.com/apollographql/apollo-feature-requests/issues/182
+ */
+
+ const fetchIntervention = (url, options) => {
+ return fetch(stripWhitespaceFromQuery(url, path), options);
+ };
+
+ const requestLink = ApolloLink.split(
+ () => useGet,
+ new HttpLink({ ...httpOptions, fetch: fetchIntervention }),
+ new BatchHttpLink(httpOptions),
+ );
+
const uploadsLink = ApolloLink.split(
(operation) => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
createUploadLink(httpOptions),
- useGet ? createHttpLink(httpOptions) : new BatchHttpLink(httpOptions),
);
const performanceBarLink = new ApolloLink((operation, forward) => {
@@ -99,6 +146,7 @@ export default (resolvers = {}, config = {}) => {
new StartupJSLink(),
apolloCaptchaLink,
uploadsLink,
+ requestLink,
]),
);
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 8b02d2567a4..7922ff22a70 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -108,25 +108,6 @@ export function getParameterValues(sParam, url = window.location) {
}
/**
- * This function accepts the `name` of the param to parse in the url
- * if the name does not exist this function will return `null`
- * otherwise it will return the value of the param key provided
- *
- * @param {String} name
- * @param {String?} urlToParse
- * @returns value of the parameter as string
- */
-export const getParameterByName = (name, urlToParse) => {
- const url = urlToParse || window.location.href;
- const parsedName = name.replace(/[[\]]/g, '\\$&');
- const regex = new RegExp(`[?&]${parsedName}(=([^&#]*)|&|#|$)`);
- const results = regex.exec(url);
- if (!results) return null;
- if (!results[2]) return '';
- return decodeUrlParameter(results[2]);
-};
-
-/**
* Merges a URL to a set of params replacing value for
* those already present.
*
@@ -514,6 +495,19 @@ export function queryToObject(query, { gatherArrays = false, legacySpacesDecode
}
/**
+ * This function accepts the `name` of the param to parse in the url
+ * if the name does not exist this function will return `null`
+ * otherwise it will return the value of the param key provided
+ *
+ * @param {String} name
+ * @param {String?} urlToParse
+ * @returns value of the parameter as string
+ */
+export const getParameterByName = (name, query = window.location.search) => {
+ return queryToObject(query)[name] || null;
+};
+
+/**
* Convert search query object back into a search query
*
* @param {Object?} params that needs to be converted
diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue
index 05b87abecd5..e01f9cc79c0 100644
--- a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue
+++ b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue
@@ -158,6 +158,12 @@ export default {
const updatedPath = setUrlParams({ branch_name: newBranch });
historyPushState(updatedPath);
+ this.$emit('updateCommitSha', { newBranch });
+
+ // refetching the content will cause a lot of components to re-render,
+ // including the text editor which uses the commit sha to register the CI schema
+ // so we need to make sure the commit sha is updated first
+ await this.$nextTick();
this.$emit('refetchContent');
},
async setSearchTerm(newSearchTerm) {
diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
index 368a026bdaa..6af3361e7e6 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
@@ -66,6 +66,7 @@ export default {
},
data() {
return {
+ commitSha: '',
hasError: false,
};
},
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_commit_sha.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/update_commit_sha.mutation.graphql
new file mode 100644
index 00000000000..dce17cad808
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/update_commit_sha.mutation.graphql
@@ -0,0 +1,3 @@
+mutation updateCommitSha($commitSha: String) {
+ updateCommitSha(commitSha: $commitSha) @client
+}
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/latest_commit_sha.query.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/latest_commit_sha.query.graphql
new file mode 100644
index 00000000000..219c23bb22b
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/latest_commit_sha.query.graphql
@@ -0,0 +1,12 @@
+query getLatestCommitSha($projectPath: ID!, $ref: String) {
+ project(fullPath: $projectPath) {
+ pipelines(ref: $ref) {
+ nodes {
+ id
+ sha
+ path
+ commitPath
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
index ad333f6d42a..2bec2006e95 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
+++ b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
@@ -1,5 +1,6 @@
import produce from 'immer';
import axios from '~/lib/utils/axios_utils';
+import getCommitShaQuery from './queries/client/commit_sha.graphql';
import getCurrentBranchQuery from './queries/client/current_branch.graphql';
import getLastCommitBranchQuery from './queries/client/last_commit_branch.query.graphql';
@@ -31,7 +32,15 @@ export const resolvers = {
__typename: 'CiLintContent',
}));
},
- updateCurrentBranch: (_, { currentBranch = undefined }, { cache }) => {
+ updateCommitSha: (_, { commitSha }, { cache }) => {
+ cache.writeQuery({
+ query: getCommitShaQuery,
+ data: produce(cache.readQuery({ query: getCommitShaQuery }), (draftData) => {
+ draftData.commitSha = commitSha;
+ }),
+ });
+ },
+ updateCurrentBranch: (_, { currentBranch }, { cache }) => {
cache.writeQuery({
query: getCurrentBranchQuery,
data: produce(cache.readQuery({ query: getCurrentBranchQuery }), (draftData) => {
@@ -39,7 +48,7 @@ export const resolvers = {
}),
});
},
- updateLastCommitBranch: (_, { lastCommitBranch = undefined }, { cache }) => {
+ updateLastCommitBranch: (_, { lastCommitBranch }, { cache }) => {
cache.writeQuery({
query: getLastCommitBranchQuery,
data: produce(cache.readQuery({ query: getLastCommitBranchQuery }), (draftData) => {
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index c7de8516c86..758c8c51a5b 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -16,12 +16,14 @@ import {
LOAD_FAILURE_UNKNOWN,
STARTER_TEMPLATE_NAME,
} from './constants';
+import updateCommitShaMutation from './graphql/mutations/update_commit_sha.mutation.graphql';
import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.graphql';
import getAppStatus from './graphql/queries/client/app_status.graphql';
import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
import getIsNewCiConfigFile from './graphql/queries/client/is_new_ci_config_file.graphql';
import getTemplate from './graphql/queries/get_starter_template.query.graphql';
+import getLatestCommitShaQuery from './graphql/queries/latest_commit_sha.query.graphql';
import PipelineEditorHome from './pipeline_editor_home.vue';
export default {
@@ -250,6 +252,38 @@ export default {
updateCiConfig(ciFileContent) {
this.currentCiFileContent = ciFileContent;
},
+ async updateCommitSha({ newBranch }) {
+ let fetchResults;
+
+ try {
+ fetchResults = await this.$apollo.query({
+ query: getLatestCommitShaQuery,
+ variables: {
+ projectPath: this.projectFullPath,
+ ref: newBranch,
+ },
+ });
+ } catch {
+ this.showFetchError();
+ return;
+ }
+
+ if (fetchResults.errors?.length > 0) {
+ this.showFetchError();
+ return;
+ }
+
+ const pipelineNodes = fetchResults?.data?.project?.pipelines?.nodes ?? [];
+ if (pipelineNodes.length === 0) {
+ return;
+ }
+
+ const commitSha = pipelineNodes[0].sha;
+ this.$apollo.mutate({
+ mutation: updateCommitShaMutation,
+ variables: { commitSha },
+ });
+ },
updateOnCommit({ type }) {
this.reportSuccess(type);
@@ -302,6 +336,7 @@ export default {
@showError="showErrorAlert"
@refetchContent="refetchContent"
@updateCiConfig="updateCiConfig"
+ @updateCommitSha="updateCommitSha"
/>
<confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" />
</div>
diff --git a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
index 07d8f3cc5f1..a0129dd536b 100644
--- a/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
+++ b/app/assets/javascripts/profile/preferences/components/profile_preferences.vue
@@ -131,7 +131,8 @@ export default {
<div class="col-lg-8">
<div class="form-group">
<gl-button
- variant="success"
+ category="primary"
+ variant="confirm"
name="commit"
type="submit"
:disabled="!isSubmitEnabled"
diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue
index 794a8a85cc5..cf1cff9023e 100644
--- a/app/assets/javascripts/repository/components/tree_content.vue
+++ b/app/assets/javascripts/repository/components/tree_content.vue
@@ -2,7 +2,7 @@
import filesQuery from 'shared_queries/repository/files.query.graphql';
import createFlash from '~/flash';
import { __ } from '../../locale';
-import { TREE_PAGE_SIZE, TREE_INITIAL_FETCH_COUNT } from '../constants';
+import { TREE_PAGE_SIZE, TREE_INITIAL_FETCH_COUNT, TREE_PAGE_LIMIT } from '../constants';
import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql';
import { readmeFile } from '../utils/readme';
@@ -36,6 +36,7 @@ export default {
return {
projectPath: '',
nextPageCursor: '',
+ pagesLoaded: 1,
entries: {
trees: [],
submodules: [],
@@ -44,16 +45,26 @@ export default {
isLoadingFiles: false,
isOverLimit: false,
clickedShowMore: false,
- pageSize: TREE_PAGE_SIZE,
fetchCounter: 0,
};
},
computed: {
+ pageSize() {
+ // we want to exponentially increase the page size to reduce the load on the frontend
+ const exponentialSize = (TREE_PAGE_SIZE / TREE_INITIAL_FETCH_COUNT) * (this.fetchCounter + 1);
+ return exponentialSize < TREE_PAGE_SIZE ? exponentialSize : TREE_PAGE_SIZE;
+ },
+ totalEntries() {
+ return Object.values(this.entries).flat().length;
+ },
readme() {
return readmeFile(this.entries.blobs);
},
+ pageLimitReached() {
+ return this.totalEntries / this.pagesLoaded >= TREE_PAGE_LIMIT;
+ },
hasShowMore() {
- return !this.clickedShowMore && this.fetchCounter === TREE_INITIAL_FETCH_COUNT;
+ return !this.clickedShowMore && this.pageLimitReached;
},
},
@@ -104,7 +115,7 @@ export default {
if (pageInfo?.hasNextPage) {
this.nextPageCursor = pageInfo.endCursor;
this.fetchCounter += 1;
- if (this.fetchCounter < TREE_INITIAL_FETCH_COUNT || this.clickedShowMore) {
+ if (!this.pageLimitReached || this.clickedShowMore) {
this.fetchFiles();
this.clickedShowMore = false;
}
@@ -127,6 +138,7 @@ export default {
},
handleShowMore() {
this.clickedShowMore = true;
+ this.pagesLoaded += 1;
this.fetchFiles();
},
},
diff --git a/app/assets/javascripts/repository/constants.js b/app/assets/javascripts/repository/constants.js
index 62d5d3db445..22349261d3c 100644
--- a/app/assets/javascripts/repository/constants.js
+++ b/app/assets/javascripts/repository/constants.js
@@ -1,4 +1,3 @@
-const TREE_PAGE_LIMIT = 1000; // the maximum amount of items per page
-
+export const TREE_PAGE_LIMIT = 1000; // the maximum amount of items per page
export const TREE_PAGE_SIZE = 100; // the amount of items to be fetched per (batch) request
export const TREE_INITIAL_FETCH_COUNT = TREE_PAGE_LIMIT / TREE_PAGE_SIZE; // the amount of (batch) requests to make
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 6668af33a1c..82d768c2351 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -240,3 +240,13 @@ $gl-line-height-42: px-to-rem(42px);
}
}
}
+
+// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1490
+.gl-w-grid-size-28 {
+ width: $grid-size * 28;
+}
+
+// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1491
+.gl-min-w-8 {
+ min-width: $gl-spacing-scale-8;
+}
diff --git a/app/graphql/resolvers/ci/template_resolver.rb b/app/graphql/resolvers/ci/template_resolver.rb
index dd910116544..7f5a1a486d7 100644
--- a/app/graphql/resolvers/ci/template_resolver.rb
+++ b/app/graphql/resolvers/ci/template_resolver.rb
@@ -6,7 +6,7 @@ module Resolvers
type Types::Ci::TemplateType, null: true
argument :name, GraphQL::STRING_TYPE, required: true,
- description: 'Name of the CI/CD template to search for.'
+ description: 'Name of the CI/CD template to search for. Template must be formatted as `Name.gitlab-ci.yml`.'
alias_method :project, :object
diff --git a/app/helpers/ci/jobs_helper.rb b/app/helpers/ci/jobs_helper.rb
index 23f2a082a68..882302f05ad 100644
--- a/app/helpers/ci/jobs_helper.rb
+++ b/app/helpers/ci/jobs_helper.rb
@@ -9,7 +9,6 @@ module Ci
"artifact_help_url" => help_page_path('user/gitlab_com/index.html', anchor: 'gitlab-cicd'),
"deployment_help_url" => help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting'),
"runner_settings_url" => project_runners_path(@build.project, anchor: 'js-runners-settings'),
- "variables_settings_url" => project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'),
"page_path" => project_job_path(@project, @build),
"build_status" => @build.status,
"build_stage" => @build.stage,
diff --git a/app/helpers/ci/pipeline_editor_helper.rb b/app/helpers/ci/pipeline_editor_helper.rb
index 1a30b6bed08..d441ffbb853 100644
--- a/app/helpers/ci/pipeline_editor_helper.rb
+++ b/app/helpers/ci/pipeline_editor_helper.rb
@@ -9,7 +9,9 @@ module Ci
end
def js_pipeline_editor_data(project)
- commit_sha = project.commit ? project.commit.sha : ''
+ initial_branch = params[:branch_name]
+ latest_commit = project.repository.commit(initial_branch) || project.commit
+ commit_sha = latest_commit ? latest_commit.sha : ''
{
"ci-config-path": project.ci_config_path_or_default,
"ci-examples-help-page-path" => help_page_path('ci/examples/index'),
@@ -17,11 +19,11 @@ module Ci
"commit-sha" => commit_sha,
"default-branch" => project.default_branch_or_main,
"empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'),
- "initial-branch-name": params[:branch_name],
+ "initial-branch-name" => initial_branch,
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
"needs-help-page-path" => help_page_path('ci/yaml/README', anchor: 'needs'),
"new-merge-request-path" => namespace_project_new_merge_request_path,
- "pipeline_etag" => project.commit ? graphql_etag_pipeline_sha_path(commit_sha) : '',
+ "pipeline_etag" => latest_commit ? graphql_etag_pipeline_sha_path(commit_sha) : '',
"pipeline-page-path" => project_pipelines_path(project),
"project-path" => project.path,
"project-full-path" => project.full_path,
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index 7679f6fce72..dc37d73df85 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -27,6 +27,9 @@ class AwardEmoji < ApplicationRecord
after_save :expire_cache
after_destroy :expire_cache
+ after_save :update_awardable_upvotes_count
+ after_destroy :update_awardable_upvotes_count
+
class << self
def votes_for_collection(ids, type)
select('name', 'awardable_id', 'COUNT(*) as count')
@@ -64,6 +67,14 @@ class AwardEmoji < ApplicationRecord
awardable.try(:bump_updated_at)
awardable.try(:expire_etag_cache)
end
+
+ private
+
+ def update_awardable_upvotes_count
+ return unless upvote? && awardable.has_attribute?(:upvotes_count)
+
+ awardable.update_column(:upvotes_count, awardable.upvotes)
+ end
end
AwardEmoji.prepend_mod_with('AwardEmoji')
diff --git a/app/models/error_tracking/error_event.rb b/app/models/error_tracking/error_event.rb
index 90f63f82a66..ed14a1bce41 100644
--- a/app/models/error_tracking/error_event.rb
+++ b/app/models/error_tracking/error_event.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ErrorTracking::ErrorEvent < ApplicationRecord
- belongs_to :error
+ belongs_to :error, counter_cache: :events_count
validates :payload, json_schema: { filename: 'error_tracking_event_payload' }
diff --git a/app/validators/json_schemas/error_tracking_event_payload.json b/app/validators/json_schemas/error_tracking_event_payload.json
index 79e81672d0e..19abde7de08 100644
--- a/app/validators/json_schemas/error_tracking_event_payload.json
+++ b/app/validators/json_schemas/error_tracking_event_payload.json
@@ -2,6 +2,9 @@
"description": "Error tracking event payload",
"type": "object",
"required": [],
+ "modules": {
+ "type": "object"
+ },
"properties": {
"event_id": {
"type": "string"
@@ -73,28 +76,7 @@
}
},
"trace": {
- "type": "object",
- "required": [],
- "properties": {
- "trace_id": {
- "type": "string"
- },
- "span_id": {
- "type": "string"
- },
- "parent_span_id": {
- "type": "string"
- },
- "description": {
- "type": "string"
- },
- "op": {
- "type": "string"
- },
- "status": {
- "type": "string"
- }
- }
+ "type": "object"
}
}
},
@@ -118,52 +100,13 @@
"type": "string"
},
"data": {
- "type": "object",
- "required": [],
- "properties": {
- "controller": {
- "type": "string"
- },
- "action": {
- "type": "string"
- },
- "params": {
- "type": "object",
- "required": [],
- "properties": {
- "controller": {
- "type": "string"
- },
- "action": {
- "type": "string"
- }
- }
- },
- "format": {
- "type": "string"
- },
- "method": {
- "type": "string"
- },
- "path": {
- "type": "string"
- },
- "start_timestamp": {
- "type": "number"
- }
- }
- },
- "level": {
- "type": "string"
+ "type": "object"
},
"message": {
"type": "string"
},
"timestamp": {
"type": "number"
- },
- "type": {
- "type": "string"
}
}
}
@@ -199,37 +142,7 @@
"type": "string"
},
"headers": {
- "type": "object",
- "required": [],
- "properties": {
- "Host": {
- "type": "string"
- },
- "User-Agent": {
- "type": "string"
- },
- "Accept": {
- "type": "string"
- },
- "Accept-Language": {
- "type": "string"
- },
- "Accept-Encoding": {
- "type": "string"
- },
- "Referer": {
- "type": "string"
- },
- "Turbolinks-Referrer": {
- "type": "string"
- },
- "Connection": {
- "type": "string"
- },
- "X-Request-Id": {
- "type": "string"
- }
- }
+ "type": "object"
},
"env": {
"type": "object",
@@ -290,25 +203,19 @@
"type": "number"
},
"in_app": {
- "type": "string"
+ "type": "boolean"
},
"filename": {
"type": "string"
},
"pre_context": {
- "type": "array",
- "items": {
- "type": "string"
- }
+ "type": "array"
},
"context_line": {
"type": "string"
},
"post_context": {
- "type": "array",
- "items": {
- "type": "string"
- }
+ "type": "array"
}
}
}