diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-11 00:09:47 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-11 00:09:47 +0300 |
commit | 7b2f94166965e60329129e5a919e2c3d88f23c7a (patch) | |
tree | 8fa395403cbffd6138b36bf36430e243f3f07f85 /app | |
parent | 6fd750c19206412cfc52b49a70b56147d839c52f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
16 files changed, 260 insertions, 59 deletions
diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js index d40d19000fb..d44bfdfb966 100644 --- a/app/assets/javascripts/editor/constants.js +++ b/app/assets/javascripts/editor/constants.js @@ -1,15 +1,15 @@ import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; -import { __ } from '~/locale'; +import { s__ } from '~/locale'; -export const SOURCE_EDITOR_INSTANCE_ERROR_NO_EL = __( - '"el" parameter is required for createInstance()', +export const SOURCE_EDITOR_INSTANCE_ERROR_NO_EL = s__( + 'SourceEditor|"el" parameter is required for createInstance()', ); export const URI_PREFIX = 'gitlab'; export const CONTENT_UPDATE_DEBOUNCE = DEFAULT_DEBOUNCE_AND_THROTTLE_MS; -export const ERROR_INSTANCE_REQUIRED_FOR_EXTENSION = __( - 'Source Editor instance is required to set up an extension.', +export const ERROR_INSTANCE_REQUIRED_FOR_EXTENSION = s__( + 'SourceEditor|Source Editor instance is required to set up an extension.', ); export const EDITOR_READY_EVENT = 'editor-ready'; @@ -20,6 +20,10 @@ export const EDITOR_TYPE_DIFF = 'vs.editor.IDiffEditor'; export const EDITOR_CODE_INSTANCE_FN = 'createInstance'; export const EDITOR_DIFF_INSTANCE_FN = 'createDiffInstance'; +export const EDITOR_EXTENSION_DEFINITION_ERROR = s__( + 'SourceEditor|Extension definition should be either a class or a function', +); + // // EXTENSIONS' CONSTANTS // diff --git a/app/assets/javascripts/editor/extensions/example_source_editor_extension.js b/app/assets/javascripts/editor/extensions/example_source_editor_extension.js new file mode 100644 index 00000000000..119a2aea9eb --- /dev/null +++ b/app/assets/javascripts/editor/extensions/example_source_editor_extension.js @@ -0,0 +1,116 @@ +// THIS IS AN EXAMPLE +// +// This file contains a basic documented example of the Source Editor extensions' +// API for your convenience. You can copy/paste it into your own file +// and adjust as you see fit +// + +export class MyFancyExtension { + /** + * THE LIFE-CYCLE CALLBACKS + */ + + /** + * Is called before the extension gets used by an instance, + * Use `onSetup` to setup Monaco directly: + * actions, keystrokes, update options, etc. + * Is called only once before the extension gets registered + * + * @param { Object } [setupOptions] The setupOptions object + * @param { Object } [instance] The Source Editor instance + */ + // eslint-disable-next-line class-methods-use-this,no-unused-vars + onSetup(setupOptions, instance) {} + + /** + * The first thing called after the extension is + * registered and used by an instance. + * Is called every time the extension is applied + * + * @param { Object } [instance] The Source Editor instance + */ + // eslint-disable-next-line class-methods-use-this,no-unused-vars + onUse(instance) {} + + /** + * Is called before un-using an extension. Can be used for time-critical + * actions like cleanup, reverting visual changes, and other user-facing + * updates. + * + * @param { Object } [instance] The Source Editor instance + */ + // eslint-disable-next-line class-methods-use-this,no-unused-vars + onBeforeUnuse(instance) {} + + /** + * Is called right after an extension is removed from an instance (un-used) + * Can be used for non time-critical tasks like cleanup on the Monaco level + * (removing actions, keystrokes, etc.). + * onUnuse() will be executed during the browser's idle period + * (https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback) + * + * @param { Object } [instance] The Source Editor instance + */ + // eslint-disable-next-line class-methods-use-this,no-unused-vars + onUnuse(instance) {} + + /** + * The public API of the extension: these are the methods that will be exposed + * to the end user + * @returns {Object} + */ + provides() { + return { + basic: () => { + // The most basic method not depending on anything + // Use: instance.basic(); + // eslint-disable-next-line @gitlab/require-i18n-strings + return 'Foo Bar'; + }, + basicWithProp: () => { + // The methods with access to the props of the extension. + // The props can be either hardcoded (for example in `onSetup`), or + // can be dynamically passed as part of `setupOptions` object when + // using the extension. + // Use: instance.use({ definition: MyFancyExtension, setupOptions: { foo: 'bar' }}); + return this.foo; + }, + basicWithPropsAsList: (prop1, prop2) => { + // Just a simple method with local props + // The props are passed as usually. + // Use: instance.basicWithPropsAsList(prop1, prop2); + // eslint-disable-next-line @gitlab/require-i18n-strings + return `The prop1 is ${prop1}; the prop2 is ${prop2}`; + }, + basicWithInstance: (instance) => { + // The method accessing the instance methods: either own or provided + // by previously-registered extensions + // `instance` is always supplied to all methods in provides() as THE LAST + // argument. + // You don't need to explicitly pass instance to this method: + // Use: instance.basicWithInstance(); + // eslint-disable-next-line @gitlab/require-i18n-strings + return `We have access to the whole Instance! ${instance.alpha()}`; + }, + advancedWithInstanceAndProps: ({ author, book } = {}, firstname, lastname, instance) => { + // Advanced method where + // { author, book } — are the props passed as an object + // prop1, prop2 — are the props passed as simple list + // instance — is automatically supplied, no need to pass it to + // the method explicitly + // Use: instance.advancedWithInstanceAndProps( + // { + // author: 'Franz Kafka', + // book: 'The Transformation' + // }, + // 'Franz', + // 'Kafka' + // ); + return ` +The author is ${author}; the book is ${book} +The author's name is ${firstname}; the last name is ${lastname} +We have access to the whole Instance! For example, 'instance.alpha()': ${instance.alpha()}`; + }, + }; + } +} diff --git a/app/assets/javascripts/editor/source_editor_extension.js b/app/assets/javascripts/editor/source_editor_extension.js new file mode 100644 index 00000000000..664bcabcf45 --- /dev/null +++ b/app/assets/javascripts/editor/source_editor_extension.js @@ -0,0 +1,17 @@ +import { EDITOR_EXTENSION_DEFINITION_ERROR } from './constants'; + +export default class EditorExtension { + constructor({ definition, setupOptions } = {}) { + if (typeof definition !== 'function') { + throw new Error(EDITOR_EXTENSION_DEFINITION_ERROR); + } + this.name = definition.name; // both class- and fn-based extensions have a name + this.setupOptions = setupOptions; + // eslint-disable-next-line new-cap + this.obj = new definition(); + } + + get api() { + return this.obj.provides(); + } +} diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue index fef5b37015c..b1ddede8fe8 100644 --- a/app/assets/javascripts/jobs/components/trigger_block.vue +++ b/app/assets/javascripts/jobs/components/trigger_block.vue @@ -84,9 +84,13 @@ export default { > </p> - <gl-table :items="trigger.variables" :fields="$options.fields" small bordered> + <gl-table :items="trigger.variables" :fields="$options.fields" small bordered fixed> + <template #cell(key)="{ item }"> + <span class="gl-overflow-break-word">{{ item.key }}</span> + </template> + <template #cell(value)="data"> - {{ getDisplayValue(data.value) }} + <span class="gl-overflow-break-word">{{ getDisplayValue(data.value) }}</span> </template> </gl-table> </template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue index ee68d7db92b..45044743e27 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue @@ -8,7 +8,6 @@ import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants'; import { PROJECT_RESOURCE_TYPE, GROUP_RESOURCE_TYPE, - LIST_QUERY_DEBOUNCE_TIME, GRAPHQL_PAGE_SIZE, } from '~/packages_and_registries/package_registry/constants'; import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql'; @@ -52,7 +51,9 @@ export default { update(data) { return data[this.graphqlResource].packages; }, - debounce: LIST_QUERY_DEBOUNCE_TIME, + skip() { + return !this.sort; + }, }, }, computed: { diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js index 50a8071ee41..27c3d5eb800 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js @@ -83,5 +83,4 @@ export const INSTANCE_PACKAGE_ENDPOINT_TYPE = 'instance'; export const PROJECT_RESOURCE_TYPE = 'project'; export const GROUP_RESOURCE_TYPE = 'group'; -export const LIST_QUERY_DEBOUNCE_TIME = 50; export const GRAPHQL_PAGE_SIZE = 20; diff --git a/app/assets/javascripts/pages/groups/packages/index/index.js b/app/assets/javascripts/pages/groups/packages/index/index.js index 95522573b53..f9eecff4ac4 100644 --- a/app/assets/javascripts/pages/groups/packages/index/index.js +++ b/app/assets/javascripts/pages/groups/packages/index/index.js @@ -1,10 +1,3 @@ -(async function packageApp() { - if (window.gon.features.packageListApollo) { - const newPackageList = await import('~/packages_and_registries/package_registry/pages/list'); +import packageList from '~/packages_and_registries/package_registry/pages/list'; - newPackageList.default(); - } else { - const packageList = await import('~/packages/list/packages_list_app_bundle'); - packageList.default(); - } -})(); +packageList(); diff --git a/app/assets/javascripts/pages/projects/packages/packages/index/index.js b/app/assets/javascripts/pages/projects/packages/packages/index/index.js index 95522573b53..f9eecff4ac4 100644 --- a/app/assets/javascripts/pages/projects/packages/packages/index/index.js +++ b/app/assets/javascripts/pages/projects/packages/packages/index/index.js @@ -1,10 +1,3 @@ -(async function packageApp() { - if (window.gon.features.packageListApollo) { - const newPackageList = await import('~/packages_and_registries/package_registry/pages/list'); +import packageList from '~/packages_and_registries/package_registry/pages/list'; - newPackageList.default(); - } else { - const packageList = await import('~/packages/list/packages_list_app_bundle'); - packageList.default(); - } -})(); +packageList(); diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql index de8de651eea..8fcae9dbad8 100644 --- a/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql +++ b/app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql @@ -18,8 +18,11 @@ query getPipelineHeaderData($fullPath: ID!, $iid: ID!) { } createdAt user { + id name + username webPath + webUrl email avatarUrl status { diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index 41613bb3307..6ace0bd88f8 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -1,10 +1,16 @@ <script> -import { GlTooltipDirective, GlLink, GlButton, GlTooltip, GlSafeHtmlDirective } from '@gitlab/ui'; +import { + GlTooltipDirective, + GlButton, + GlSafeHtmlDirective, + GlAvatarLink, + GlAvatarLabeled, +} from '@gitlab/ui'; +import { isGid, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { glEmojiTag } from '../../emoji'; import { __, sprintf } from '../../locale'; import CiIconBadge from './ci_badge_link.vue'; import TimeagoTooltip from './time_ago_tooltip.vue'; -import UserAvatarImage from './user_avatar/user_avatar_image.vue'; /** * Renders header component for job and pipeline page based on UI mockups @@ -17,10 +23,9 @@ export default { components: { CiIconBadge, TimeagoTooltip, - UserAvatarImage, - GlLink, GlButton, - GlTooltip, + GlAvatarLink, + GlAvatarLabeled, }, directives: { GlTooltip: GlTooltipDirective, @@ -94,6 +99,9 @@ export default { return this.itemName; }, + userId() { + return isGid(this.user?.id) ? getIdFromGraphQLId(this.user?.id) : this.user?.id; + }, }, methods: { @@ -124,24 +132,32 @@ export default { {{ __('by') }} <template v-if="user"> - <gl-link - v-gl-tooltip - :href="userPath" - :title="user.email" - class="js-user-link commit-committer-link" + <gl-avatar-link + :data-user-id="userId" + :data-username="user.username" + :data-name="user.name" + :href="user.webUrl" + target="_blank" + class="js-user-link gl-vertical-align-middle gl-mx-2 gl-align-items-center" > - <user-avatar-image :img-src="avatarUrl" :img-alt="userAvatarAltText" :size="24" /> - {{ user.name }} - </gl-link> - <gl-tooltip v-if="message" :target="() => $refs[$options.EMOJI_REF]"> - {{ message }} - </gl-tooltip> - <span - v-if="statusTooltipHTML" - :ref="$options.EMOJI_REF" - v-safe-html:[$options.safeHtmlConfig]="statusTooltipHTML" - :data-testid="message" - ></span> + <gl-avatar-labeled + :size="24" + :src="avatarUrl" + :label="user.name" + class="gl-display-none gl-sm-display-inline-flex gl-mx-1" + /> + <strong class="author gl-display-inline gl-sm-display-none!">@{{ user.username }}</strong> + <gl-tooltip v-if="message" :target="() => $refs[$options.EMOJI_REF]"> + {{ message }} + </gl-tooltip> + <span + v-if="statusTooltipHTML" + :ref="$options.EMOJI_REF" + v-safe-html:[$options.safeHtmlConfig]="statusTooltipHTML" + class="gl-ml-2" + :data-testid="message" + ></span> + </gl-avatar-link> </template> </section> diff --git a/app/controllers/groups/packages_controller.rb b/app/controllers/groups/packages_controller.rb index d02a8262948..47f1816cc4c 100644 --- a/app/controllers/groups/packages_controller.rb +++ b/app/controllers/groups/packages_controller.rb @@ -6,10 +6,6 @@ module Groups feature_category :package_registry - before_action do - push_frontend_feature_flag(:package_list_apollo, default_enabled: :yaml) - end - private def verify_packages_enabled! diff --git a/app/controllers/projects/packages/packages_controller.rb b/app/controllers/projects/packages/packages_controller.rb index dd7c2ad3cbd..5de71466c10 100644 --- a/app/controllers/projects/packages/packages_controller.rb +++ b/app/controllers/projects/packages/packages_controller.rb @@ -7,10 +7,6 @@ module Projects feature_category :package_registry - before_action do - push_frontend_feature_flag(:package_list_apollo, default_enabled: :yaml) - end - def show @package = project.packages.find(params[:id]) end diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb index ff564d87449..f1ca5c23997 100644 --- a/app/models/suggestion.rb +++ b/app/models/suggestion.rb @@ -50,6 +50,7 @@ class Suggestion < ApplicationRecord next _("Can't apply as the source branch was deleted.") unless noteable.source_branch_exists? next outdated_reason if outdated?(cached: cached) || !note.active? next _("This suggestion already matches its content.") unless different_content? + next _("This file was modified for readability, and can't accept suggestions. Edit it directly.") if file_path.end_with? "ipynb" end end diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb index 62aead352aa..94629ae7609 100644 --- a/app/services/system_notes/issuables_service.rb +++ b/app/services/system_notes/issuables_service.rb @@ -176,7 +176,13 @@ module SystemNotes body = cross_reference_note_content(gfm_reference) if noteable.is_a?(ExternalIssue) - noteable.project.external_issue_tracker.create_cross_reference_note(noteable, mentioner, author) + Integrations::CreateExternalCrossReferenceWorker.perform_async( + noteable.project_id, + noteable.id, + mentioner.class.name, + mentioner.id, + author.id + ) else track_cross_reference_action create_note(NoteSummary.new(noteable, noteable.project, author, body, action: 'cross_reference')) diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index ae39bd60233..4fa74195a99 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -2231,6 +2231,15 @@ :weight: 2 :idempotent: true :tags: [] +- :name: integrations_create_external_cross_reference + :worker_name: Integrations::CreateExternalCrossReferenceWorker + :feature_category: :integrations + :has_external_dependencies: + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: invalid_gpg_signature_update :worker_name: InvalidGpgSignatureUpdateWorker :feature_category: :source_code_management diff --git a/app/workers/integrations/create_external_cross_reference_worker.rb b/app/workers/integrations/create_external_cross_reference_worker.rb new file mode 100644 index 00000000000..02c1315249e --- /dev/null +++ b/app/workers/integrations/create_external_cross_reference_worker.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Integrations + class CreateExternalCrossReferenceWorker + include ApplicationWorker + + data_consistency :delayed + + feature_category :integrations + urgency :low + idempotent! + deduplicate :until_executed, including_scheduled: true + loggable_arguments 2 + + def perform(project_id, external_issue_id, mentionable_type, mentionable_id, author_id) + project = Project.find_by_id(project_id) || return + author = User.find_by_id(author_id) || return + mentionable = find_mentionable(mentionable_type, mentionable_id, project) || return + external_issue = ExternalIssue.new(external_issue_id, project) + + project.external_issue_tracker.create_cross_reference_note( + external_issue, + mentionable, + author + ) + end + + private + + def find_mentionable(mentionable_type, mentionable_id, project) + mentionable_class = mentionable_type.safe_constantize + + # Passing an invalid mentionable_class is a developer error, so we don't want to retry the job + # but still track the exception on production, and raise it in development. + unless mentionable_class && mentionable_class < Mentionable + Gitlab::ErrorTracking.track_and_raise_for_dev_exception(ArgumentError.new("Unexpected class '#{mentionable_type}' is not a Mentionable")) + return + end + + if mentionable_type == 'Commit' + project.commit(mentionable_id) + else + mentionable_class.find_by_id(mentionable_id) + end + end + end +end |