diff options
75 files changed, 1323 insertions, 530 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 0bf6709a0ba..e61829399ec 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -30ae36f781ee979330b1f170d81c97c319c2fff1 +4a1b0d4018ee35cfe786ba3dd975b20013a39e39 diff --git a/app/assets/javascripts/ide/messages.js b/app/assets/javascripts/ide/messages.js index 189226ef835..fe8eba823a8 100644 --- a/app/assets/javascripts/ide/messages.js +++ b/app/assets/javascripts/ide/messages.js @@ -1,11 +1,11 @@ import { s__ } from '~/locale'; export const MSG_CANNOT_PUSH_CODE_SHOULD_FORK = s__( - 'WebIDE|You need permission to edit files directly in this project. Fork this project to make your changes and submit a merge request.', + 'WebIDE|You can’t edit files directly in this project. Fork this project and submit a merge request with your changes.', ); export const MSG_CANNOT_PUSH_CODE_GO_TO_FORK = s__( - 'WebIDE|You need permission to edit files directly in this project. Go to your fork to make changes and submit a merge request.', + 'WebIDE|You can’t edit files directly in this project. Go to your fork and submit a merge request with your changes.', ); export const MSG_CANNOT_PUSH_CODE = s__( @@ -13,7 +13,7 @@ export const MSG_CANNOT_PUSH_CODE = s__( ); export const MSG_CANNOT_PUSH_UNSIGNED = s__( - 'WebIDE|This project does not accept unsigned commits. You will not be able to commit your changes through the Web IDE.', + 'WebIDE|This project does not accept unsigned commits. You can’t commit changes through the Web IDE.', ); export const MSG_CANNOT_PUSH_UNSIGNED_SHORT = s__( 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 142438bec21..d443f5b4b1d 100644 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue @@ -37,6 +37,7 @@ import { __ } from '~/locale'; import { DEFAULT_NONE_ANY } from '~/vue_shared/components/filtered_search_bar/constants'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue'; +import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_token.vue'; import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; @@ -87,6 +88,9 @@ export default { exportCsvPath: { default: '', }, + groupEpicsPath: { + default: '', + }, hasBlockedIssuesFeature: { default: false, }, @@ -241,6 +245,17 @@ export default { }); } + if (this.groupEpicsPath) { + tokens.push({ + type: 'epic_id', + title: __('Epic'), + icon: 'epic', + token: EpicToken, + unique: true, + fetchEpics: this.fetchEpics, + }); + } + if (this.hasIssueWeightsFeature) { tokens.push({ type: 'weight', @@ -306,6 +321,16 @@ export default { fetchEmojis(search) { return this.fetchWithCache(this.autocompleteAwardEmojisPath, 'emojis', 'name', search); }, + async fetchEpics(search) { + const epics = await this.fetchWithCache(this.groupEpicsPath, 'epics'); + if (!search) { + return epics.slice(0, MAX_LIST_SIZE); + } + const number = Number(search); + return Number.isNaN(number) + ? fuzzaldrinPlus.filter(epics, search, { key: 'title' }) + : epics.filter((epic) => epic.id === number); + }, fetchLabels(search) { return this.fetchWithCache(this.projectLabelsPath, 'labels', 'title', search); }, diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js index 3b01d0df523..c0441758558 100644 --- a/app/assets/javascripts/issues_list/constants.js +++ b/app/assets/javascripts/issues_list/constants.js @@ -324,6 +324,26 @@ export const filters = { }, }, }, + epic_id: { + apiParam: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'epic_id', + [SPECIAL_FILTER]: 'epic_id', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[epic_id]', + }, + }, + urlParam: { + [OPERATOR_IS]: { + [NORMAL_FILTER]: 'epic_id', + [SPECIAL_FILTER]: 'epic_id', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[epic_id]', + }, + }, + }, weight: { apiParam: { [OPERATOR_IS]: { diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js index d543643b003..c4bd62bce59 100644 --- a/app/assets/javascripts/issues_list/index.js +++ b/app/assets/javascripts/issues_list/index.js @@ -85,6 +85,7 @@ export function mountIssuesListApp() { emptyStateSvgPath, endpoint, exportCsvPath, + groupEpicsPath, hasBlockedIssuesFeature, hasIssuableHealthStatusFeature, hasIssues, @@ -121,6 +122,7 @@ export function mountIssuesListApp() { canBulkUpdate: parseBoolean(canBulkUpdate), emptyStateSvgPath, endpoint, + groupEpicsPath, hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature), hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature), hasIssues: parseBoolean(hasIssues), diff --git a/app/assets/javascripts/members/index.js b/app/assets/javascripts/members/index.js index 6376b3fa75a..6c913af8a0f 100644 --- a/app/assets/javascripts/members/index.js +++ b/app/assets/javascripts/members/index.js @@ -1,7 +1,7 @@ import { GlToast } from '@gitlab/ui'; import Vue from 'vue'; import Vuex from 'vuex'; -import { parseDataAttributes } from 'ee_else_ce/members/utils'; +import { parseDataAttributes } from '~/members/utils'; import App from './components/app.vue'; import membersStore from './store'; diff --git a/app/assets/javascripts/members/utils.js b/app/assets/javascripts/members/utils.js index 031438d6ace..be549b40885 100644 --- a/app/assets/javascripts/members/utils.js +++ b/app/assets/javascripts/members/utils.js @@ -1,9 +1,5 @@ import { isUndefined } from 'lodash'; -import { - getParameterByName, - convertObjectPropsToCamelCase, - parseBoolean, -} from '~/lib/utils/common_utils'; +import { getParameterByName, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { setUrlParams } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import { @@ -105,18 +101,12 @@ export const buildSortHref = ({ export const canOverride = () => false; export const parseDataAttributes = (el) => { - const { members, pagination, sourceId, memberPath, canManageMembers } = el.dataset; + const { membersData } = el.dataset; - return { - members: convertObjectPropsToCamelCase(JSON.parse(members), { deep: true }), - pagination: convertObjectPropsToCamelCase(JSON.parse(pagination || '{}'), { - deep: true, - ignoreKeyNames: ['params'], - }), - sourceId: parseInt(sourceId, 10), - memberPath, - canManageMembers: parseBoolean(canManageMembers), - }; + return convertObjectPropsToCamelCase(JSON.parse(membersData), { + deep: true, + ignoreKeyNames: ['params'], + }); }; export const baseRequestFormatter = (basePropertyName, accessLevelPropertyName) => ({ diff --git a/app/assets/javascripts/packages/details/components/maven_installation.vue b/app/assets/javascripts/packages/details/components/maven_installation.vue index b9532cb7e72..6974de99344 100644 --- a/app/assets/javascripts/packages/details/components/maven_installation.vue +++ b/app/assets/javascripts/packages/details/components/maven_installation.vue @@ -28,10 +28,15 @@ export default { 'mavenSetupXml', 'gradleGroovyInstalCommand', 'gradleGroovyAddSourceCommand', + 'gradleKotlinInstalCommand', + 'gradleKotlinAddSourceCommand', ]), showMaven() { return this.instructionType === 'maven'; }, + showGroovy() { + return this.instructionType === 'groovy'; + }, }, i18n: { xmlText: s__( @@ -47,8 +52,9 @@ export default { trackingActions: { ...TrackingActions }, TrackingLabels, installOptions: [ - { value: 'maven', label: s__('PackageRegistry|Show Maven commands') }, - { value: 'groovy', label: s__('PackageRegistry|Show Gradle Groovy DSL commands') }, + { value: 'maven', label: s__('PackageRegistry|Maven XML') }, + { value: 'groovy', label: s__('PackageRegistry|Gradle Groovy DSL') }, + { value: 'kotlin', label: s__('PackageRegistry|Gradle Kotlin DSL') }, ], }; </script> @@ -107,7 +113,7 @@ export default { </template> </gl-sprintf> </template> - <template v-else> + <template v-else-if="showGroovy"> <code-instruction class="gl-mb-5" :label="s__('PackageRegistry|Gradle Groovy DSL install command')" @@ -125,5 +131,23 @@ export default { multiline /> </template> + <template v-else> + <code-instruction + class="gl-mb-5" + :label="s__('PackageRegistry|Gradle Kotlin DSL install command')" + :instruction="gradleKotlinInstalCommand" + :copy-text="s__('PackageRegistry|Copy Gradle Kotlin DSL install command')" + :tracking-action="$options.trackingActions.COPY_KOTLIN_INSTALL_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + /> + <code-instruction + :label="s__('PackageRegistry|Add Gradle Kotlin DSL repository command')" + :instruction="gradleKotlinAddSourceCommand" + :copy-text="s__('PackageRegistry|Copy add Gradle Kotlin DSL repository command')" + :tracking-action="$options.trackingActions.COPY_KOTLIN_ADD_TO_SOURCE_COMMAND" + :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION" + multiline + /> + </template> </div> </template> diff --git a/app/assets/javascripts/packages/details/constants.js b/app/assets/javascripts/packages/details/constants.js index f0300ee29b4..cd34b1ad45a 100644 --- a/app/assets/javascripts/packages/details/constants.js +++ b/app/assets/javascripts/packages/details/constants.js @@ -38,6 +38,9 @@ export const TrackingActions = { COPY_GRADLE_INSTALL_COMMAND: 'copy_gradle_install_command', COPY_GRADLE_ADD_TO_SOURCE_COMMAND: 'copy_gradle_add_to_source_command', + + COPY_KOTLIN_INSTALL_COMMAND: 'copy_kotlin_install_command', + COPY_KOTLIN_ADD_TO_SOURCE_COMMAND: 'copy_kotlin_add_to_source_command', }; export const NpmManager = { diff --git a/app/assets/javascripts/packages/details/store/getters.js b/app/assets/javascripts/packages/details/store/getters.js index fb9b7d61fd2..ae273e26d6a 100644 --- a/app/assets/javascripts/packages/details/store/getters.js +++ b/app/assets/javascripts/packages/details/store/getters.js @@ -126,4 +126,15 @@ export const gradleGroovyAddSourceCommand = ({ mavenPath }) => url '${mavenPath}' }`; +export const gradleKotlinInstalCommand = ({ packageEntity }) => { + const { + app_group: group = '', + app_name: name = '', + app_version: version = '', + } = packageEntity.maven_metadatum; + return `implementation("${group}:${name}:${version}")`; +}; + +export const gradleKotlinAddSourceCommand = ({ mavenPath }) => `maven("${mavenPath}")`; + export const groupExists = ({ groupListUrl }) => groupListUrl.length > 0; diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue index 101c7150c55..1450807b11d 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/epic_token.vue @@ -1,15 +1,18 @@ <script> -import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui'; +import { + GlDropdownDivider, + GlFilteredSearchSuggestion, + GlFilteredSearchToken, + GlLoadingIcon, +} from '@gitlab/ui'; import { debounce } from 'lodash'; - import createFlash from '~/flash'; -import { isNumeric } from '~/lib/utils/number_utils'; import { __ } from '~/locale'; -import { DEBOUNCE_DELAY } from '../constants'; -import { stripQuotes } from '../filtered_search_utils'; +import { DEBOUNCE_DELAY, DEFAULT_NONE_ANY } from '../constants'; export default { components: { + GlDropdownDivider, GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon, @@ -32,29 +35,16 @@ export default { }, computed: { currentValue() { - /* - * When the URL contains the epic_iid, we'd get: '123' - */ - if (isNumeric(this.value.data)) { - return parseInt(this.value.data, 10); - } - - /* - * When the token is added in current session it'd be: 'Foo::&123' - */ - const id = this.value.data.split('::&')[1]; - - if (id) { - return parseInt(id, 10); - } - - return this.value.data; + return Number(this.value.data); + }, + defaultEpics() { + return this.config.defaultEpics || DEFAULT_NONE_ANY; + }, + idProperty() { + return this.config.idProperty || 'id'; }, activeEpic() { - const currentValueIsString = typeof this.currentValue === 'string'; - return this.epics.find( - (epic) => epic[currentValueIsString ? 'title' : 'iid'] === this.currentValue, - ); + return this.epics.find((epic) => epic[this.idProperty] === this.currentValue); }, }, watch: { @@ -72,20 +62,8 @@ export default { this.loading = true; this.config .fetchEpics(searchTerm) - .then(({ data }) => { - this.epics = data; - }) - .catch(() => createFlash({ message: __('There was a problem fetching epics.') })) - .finally(() => { - this.loading = false; - }); - }, - fetchSingleEpic(iid) { - this.loading = true; - this.config - .fetchSingleEpic(iid) - .then(({ data }) => { - this.epics = [data]; + .then((response) => { + this.epics = Array.isArray(response) ? response : response.data; }) .catch(() => createFlash({ message: __('There was a problem fetching epics.') })) .finally(() => { @@ -93,17 +71,13 @@ export default { }); }, searchEpics: debounce(function debouncedSearch({ data }) { - if (isNumeric(data)) { - return this.fetchSingleEpic(data); - } - return this.fetchEpicsBySearchTerm(data); + this.fetchEpicsBySearchTerm(data); }, DEBOUNCE_DELAY), - getEpicValue(epic) { - return `${epic.title}::&${epic.iid}`; + getEpicDisplayText(epic) { + return `${epic.title}::&${epic[this.idProperty]}`; }, }, - stripQuotes, }; </script> @@ -115,17 +89,25 @@ export default { @input="searchEpics" > <template #view="{ inputValue }"> - <span>{{ activeEpic ? getEpicValue(activeEpic) : $options.stripQuotes(inputValue) }}</span> + {{ activeEpic ? getEpicDisplayText(activeEpic) : inputValue }} </template> <template #suggestions> + <gl-filtered-search-suggestion + v-for="epic in defaultEpics" + :key="epic.value" + :value="epic.value" + > + {{ epic.text }} + </gl-filtered-search-suggestion> + <gl-dropdown-divider v-if="defaultEpics.length" /> <gl-loading-icon v-if="loading" /> <template v-else> <gl-filtered-search-suggestion v-for="epic in epics" - :key="epic.id" - :value="getEpicValue(epic)" + :key="epic[idProperty]" + :value="String(epic[idProperty])" > - <div>{{ epic.title }}</div> + {{ epic.title }} </gl-filtered-search-suggestion> </template> </template> diff --git a/app/controllers/concerns/accepts_pending_invitations.rb b/app/controllers/concerns/accepts_pending_invitations.rb index d70c9929a7e..5601b7a7f79 100644 --- a/app/controllers/concerns/accepts_pending_invitations.rb +++ b/app/controllers/concerns/accepts_pending_invitations.rb @@ -6,7 +6,8 @@ module AcceptsPendingInvitations def accept_pending_invitations return unless resource.active_for_authentication? - if resource.accept_pending_invitations!.any? + if resource.pending_invitations.load.any? + resource.accept_pending_invitations! clear_stored_location_for_resource after_pending_invitations_hook end diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb index d225d5e104c..fa6adc9431d 100644 --- a/app/controllers/projects/runner_projects_controller.rb +++ b/app/controllers/projects/runner_projects_controller.rb @@ -17,7 +17,10 @@ class Projects::RunnerProjectsController < Projects::ApplicationController if @runner.assign_to(project, current_user) redirect_to path else - redirect_to path, alert: 'Failed adding runner to project' + assign_to_messages = @runner.errors.messages[:assign_to] + alert = assign_to_messages&.join(',') || 'Failed adding runner to project' + + redirect_to path, alert: alert end end diff --git a/app/graphql/types/repository/blob_type.rb b/app/graphql/types/repository/blob_type.rb index 004bceea154..8ed97d7e663 100644 --- a/app/graphql/types/repository/blob_type.rb +++ b/app/graphql/types/repository/blob_type.rb @@ -32,6 +32,15 @@ module Types field :web_path, GraphQL::STRING_TYPE, null: true, description: 'Web path of the blob.' + field :ide_edit_path, GraphQL::STRING_TYPE, null: true, + description: 'Web path to edit this blob in the Web IDE.' + + field :fork_and_edit_path, GraphQL::STRING_TYPE, null: true, + description: 'Web path to edit this blob using a forked project.' + + field :ide_fork_and_edit_path, GraphQL::STRING_TYPE, null: true, + description: 'Web path to edit this blob in the Web IDE using a forked project.' + field :size, GraphQL::INT_TYPE, null: true, description: 'Size (in bytes) of the blob.' @@ -53,6 +62,9 @@ module Types field :raw_path, GraphQL::STRING_TYPE, null: true, description: 'Web path to download the raw blob.' + field :external_storage_url, GraphQL::STRING_TYPE, null: true, + description: 'Web path to download the raw blob via external storage, if enabled.' + field :replace_path, GraphQL::STRING_TYPE, null: true, description: 'Web path to replace the blob content.' @@ -72,6 +84,10 @@ module Types null: true, calls_gitaly: true + field :can_modify_blob, GraphQL::BOOLEAN_TYPE, null: true, method: :can_modify_blob?, + calls_gitaly: true, + description: 'Whether the current user can modify the blob.' + def raw_text_blob object.data unless object.binary? end diff --git a/app/helpers/groups/group_members_helper.rb b/app/helpers/groups/group_members_helper.rb index 4bd18b62e0d..061ad7d0d3b 100644 --- a/app/helpers/groups/group_members_helper.rb +++ b/app/helpers/groups/group_members_helper.rb @@ -13,31 +13,41 @@ module Groups::GroupMembersHelper render 'shared/members/invite_member', submit_url: group_group_members_path(group), access_levels: group.access_level_roles, default_access_level: default_access_level end - def group_group_links_data_json(group_links) - GroupLink::GroupGroupLinkSerializer.new.represent(group_links, { current_user: current_user }).to_json + def group_members_list_data_json(group, members, pagination = {}) + group_members_list_data(group, members, pagination).to_json end - def members_data_json(group, members) - MemberSerializer.new.represent(members, { current_user: current_user, group: group, source: group }).to_json + def group_group_links_list_data_json(group) + group_group_links_list_data(group).to_json + end + + private + + def group_members_serialized(group, members) + MemberSerializer.new.represent(members, { current_user: current_user, group: group, source: group }) + end + + def group_group_links_serialized(group_links) + GroupLink::GroupGroupLinkSerializer.new.represent(group_links, { current_user: current_user }) end # Overridden in `ee/app/helpers/ee/groups/group_members_helper.rb` - def group_members_list_data_attributes(group, members, pagination = {}) + def group_members_list_data(group, members, pagination) { - members: members_data_json(group, members), - pagination: members_pagination_data_json(members, pagination), + members: group_members_serialized(group, members), + pagination: members_pagination_data(members, pagination), member_path: group_group_member_path(group, ':id'), source_id: group.id, - can_manage_members: can?(current_user, :admin_group_member, group).to_s + can_manage_members: can?(current_user, :admin_group_member, group) } end - def group_group_links_list_data_attributes(group) + def group_group_links_list_data(group) group_links = group.shared_with_group_links { - members: group_group_links_data_json(group_links), - pagination: members_pagination_data_json(group_links), + members: group_group_links_serialized(group_links), + pagination: members_pagination_data(group_links), member_path: group_group_link_path(group, ':id'), source_id: group.id } diff --git a/app/helpers/members_helper.rb b/app/helpers/members_helper.rb index 4261f0660d5..d3db5d24207 100644 --- a/app/helpers/members_helper.rb +++ b/app/helpers/members_helper.rb @@ -66,13 +66,13 @@ module MembersHelper 'group and any subresources' end - def members_pagination_data_json(members, pagination = {}) + def members_pagination_data(members, pagination = {}) { current_page: members.respond_to?(:current_page) ? members.current_page : nil, per_page: members.respond_to?(:limit_value) ? members.limit_value : nil, total_items: members.respond_to?(:total_count) ? members.total_count : members.count, param_name: pagination[:param_name] || nil, params: pagination[:params] || {} - }.to_json + } end end diff --git a/app/helpers/projects/project_members_helper.rb b/app/helpers/projects/project_members_helper.rb index d3559c14d0d..fa68bbad135 100644 --- a/app/helpers/projects/project_members_helper.rb +++ b/app/helpers/projects/project_members_helper.rb @@ -27,31 +27,41 @@ module Projects::ProjectMembersHelper project.group.has_owner?(current_user) end - def project_group_links_data_json(group_links) - GroupLink::ProjectGroupLinkSerializer.new.represent(group_links, { current_user: current_user }).to_json + def project_members_list_data_json(project, members, pagination = {}) + project_members_list_data(project, members, pagination).to_json end - def project_members_data_json(project, members) - MemberSerializer.new.represent(members, { current_user: current_user, group: project.group, source: project }).to_json + def project_group_links_list_data_json(project, group_links) + project_group_links_list_data(project, group_links).to_json end - def project_members_list_data_attributes(project, members, pagination = {}) + private + + def project_members_serialized(project, members) + MemberSerializer.new.represent(members, { current_user: current_user, group: project.group, source: project }) + end + + def project_group_links_serialized(group_links) + GroupLink::ProjectGroupLinkSerializer.new.represent(group_links, { current_user: current_user }) + end + + def project_members_list_data(project, members, pagination) { - members: project_members_data_json(project, members), - pagination: members_pagination_data_json(members, pagination), + members: project_members_serialized(project, members), + pagination: members_pagination_data(members, pagination), member_path: project_project_member_path(project, ':id'), source_id: project.id, - can_manage_members: can_manage_project_members?(project).to_s + can_manage_members: can_manage_project_members?(project) } end - def project_group_links_list_data_attributes(project, group_links) + def project_group_links_list_data(project, group_links) { - members: project_group_links_data_json(group_links), - pagination: members_pagination_data_json(group_links), + members: project_group_links_serialized(group_links), + pagination: members_pagination_data(group_links), member_path: project_group_link_path(project, ':id'), source_id: project.id, - can_manage_members: can_manage_project_members?(project).to_s + can_manage_members: can_manage_project_members?(project) } end end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 05126853e0f..6b0c0a57877 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -46,9 +46,9 @@ module Ci MINUTES_COST_FACTOR_FIELDS = %i[public_projects_minutes_cost_factor private_projects_minutes_cost_factor].freeze has_many :builds - has_many :runner_projects, inverse_of: :runner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :runner_projects, inverse_of: :runner, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :projects, through: :runner_projects - has_many :runner_namespaces, inverse_of: :runner + has_many :runner_namespaces, inverse_of: :runner, autosave: true has_many :groups, through: :runner_namespaces has_one :last_build, -> { order('id DESC') }, class_name: 'Ci::Build' diff --git a/app/models/concerns/packages/debian/architecture.rb b/app/models/concerns/packages/debian/architecture.rb index 760ebb49980..e2fa0ceb0f6 100644 --- a/app/models/concerns/packages/debian/architecture.rb +++ b/app/models/concerns/packages/debian/architecture.rb @@ -23,6 +23,7 @@ module Packages uniqueness: { scope: %i[distribution_id] }, format: { with: Gitlab::Regex.debian_architecture_regex } + scope :ordered_by_name, -> { order(:name) } scope :with_distribution, ->(distribution) { where(distribution: distribution) } scope :with_name, ->(name) { where(name: name) } end diff --git a/app/models/concerns/packages/debian/component.rb b/app/models/concerns/packages/debian/component.rb index 7b342c7b684..5ea686faec2 100644 --- a/app/models/concerns/packages/debian/component.rb +++ b/app/models/concerns/packages/debian/component.rb @@ -23,6 +23,7 @@ module Packages uniqueness: { scope: %i[distribution_id] }, format: { with: Gitlab::Regex.debian_component_regex } + scope :ordered_by_name, -> { order(:name) } scope :with_distribution, ->(distribution) { where(distribution: distribution) } scope :with_name, ->(name) { where(name: name) } end diff --git a/app/models/concerns/packages/debian/component_file.rb b/app/models/concerns/packages/debian/component_file.rb index 3cc2c291e96..c41635a0d16 100644 --- a/app/models/concerns/packages/debian/component_file.rb +++ b/app/models/concerns/packages/debian/component_file.rb @@ -60,6 +60,8 @@ module Packages scope :preload_distribution, -> { includes(component: :distribution) } + scope :created_before, ->(reference) { where("#{table_name}.created_at < ?", reference) } + mount_file_store_uploader Packages::Debian::ComponentFileUploader before_validation :update_size_from_file diff --git a/app/models/email.rb b/app/models/email.rb index c5154267ff0..0140f784842 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -22,7 +22,7 @@ class Email < ApplicationRecord self.reconfirmable = false # currently email can't be changed, no need to reconfirm - delegate :username, :can?, to: :user + delegate :username, :can?, :pending_invitations, :accept_pending_invitations!, to: :user def email=(value) write_attribute(:email, value.downcase.strip) @@ -32,10 +32,6 @@ class Email < ApplicationRecord self.errors.add(:email, 'has already been taken') if User.exists?(email: self.email) end - def accept_pending_invitations! - user.accept_pending_invitations! - end - def validate_email_format self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email) end diff --git a/app/models/packages/debian/group_distribution.rb b/app/models/packages/debian/group_distribution.rb index eea7acacc96..50c1ec9f163 100644 --- a/app/models/packages/debian/group_distribution.rb +++ b/app/models/packages/debian/group_distribution.rb @@ -6,4 +6,14 @@ class Packages::Debian::GroupDistribution < ApplicationRecord end include Packages::Debian::Distribution + + def packages + Packages::Package + .for_projects(group.all_projects.public_only) + .with_debian_codename(codename) + end + + def package_files + ::Packages::PackageFile.for_package_ids(packages.select(:id)) + end end diff --git a/app/models/packages/debian/project_distribution.rb b/app/models/packages/debian/project_distribution.rb index 22f1008b3b5..5ac60d789b3 100644 --- a/app/models/packages/debian/project_distribution.rb +++ b/app/models/packages/debian/project_distribution.rb @@ -5,8 +5,9 @@ class Packages::Debian::ProjectDistribution < ApplicationRecord :project end + include Packages::Debian::Distribution + has_many :publications, class_name: 'Packages::Debian::Publication', inverse_of: :distribution, foreign_key: :distribution_id has_many :packages, class_name: 'Packages::Package', through: :publications - - include Packages::Debian::Distribution + has_many :package_files, class_name: 'Packages::PackageFile', through: :packages end diff --git a/app/models/packages/package_file.rb b/app/models/packages/package_file.rb index 377e4fcf063..dbd25de12ea 100644 --- a/app/models/packages/package_file.rb +++ b/app/models/packages/package_file.rb @@ -5,7 +5,7 @@ class Packages::PackageFile < ApplicationRecord delegate :project, :project_id, to: :package delegate :conan_file_type, to: :conan_file_metadatum - delegate :file_type, :architecture, :fields, to: :debian_file_metadatum, prefix: :debian + delegate :file_type, :component, :architecture, :fields, to: :debian_file_metadatum, prefix: :debian delegate :channel, :metadata, to: :helm_file_metadatum, prefix: :helm belongs_to :package @@ -27,6 +27,7 @@ class Packages::PackageFile < ApplicationRecord validates :file_name, uniqueness: { scope: :package }, if: -> { package&.pypi? } scope :recent, -> { order(id: :desc) } + scope :for_package_ids, ->(ids) { where(package_id: ids) } scope :with_file_name, ->(file_name) { where(file_name: file_name) } scope :with_file_name_like, ->(file_name) { where(arel_table[:file_name].matches(file_name)) } scope :with_files_stored_locally, -> { where(file_store: ::Packages::PackageFileUploader::Store::LOCAL) } @@ -44,7 +45,17 @@ class Packages::PackageFile < ApplicationRecord scope :with_debian_file_type, ->(file_type) do joins(:debian_file_metadatum) - .where(packages_debian_file_metadata: { debian_file_type: ::Packages::Debian::FileMetadatum.debian_file_types[file_type] }) + .where(packages_debian_file_metadata: { file_type: ::Packages::Debian::FileMetadatum.file_types[file_type] }) + end + + scope :with_debian_component_name, ->(component_name) do + joins(:debian_file_metadatum) + .where(packages_debian_file_metadata: { component: component_name }) + end + + scope :with_debian_architecture_name, ->(architecture_name) do + joins(:debian_file_metadatum) + .where(packages_debian_file_metadata: { architecture: architecture_name }) end scope :with_conan_package_reference, ->(conan_package_reference) do diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb index cfe2bba8aa5..56dd056b9bc 100644 --- a/app/presenters/blob_presenter.rb +++ b/app/presenters/blob_presenter.rb @@ -1,6 +1,12 @@ # frozen_string_literal: true class BlobPresenter < Gitlab::View::Presenter::Delegated + include ApplicationHelper + include BlobHelper + include DiffHelper + include TreeHelper + include ChecksCollaboration + presents :blob def highlight(to: nil, plain: nil) @@ -40,6 +46,28 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated url_helpers.project_create_blob_path(project, ref_qualified_path) end + def fork_and_edit_path + fork_path_for_current_user(project, edit_blob_path) + end + + def ide_fork_and_edit_path + fork_path_for_current_user(project, ide_edit_path) + end + + def can_modify_blob? + super(blob, project, blob.commit_id) + end + + def ide_edit_path + super(project, blob.commit_id, blob.path) + end + + def external_storage_url + return unless static_objects_external_storage_enabled? + + external_storage_url_or_path(url_helpers.project_raw_url(project, ref_qualified_path)) + end + private def url_helpers diff --git a/app/services/packages/debian/generate_distribution_service.rb b/app/services/packages/debian/generate_distribution_service.rb new file mode 100644 index 00000000000..67348af1a49 --- /dev/null +++ b/app/services/packages/debian/generate_distribution_service.rb @@ -0,0 +1,207 @@ +# frozen_string_literal: true + +module Packages + module Debian + class GenerateDistributionService + include Gitlab::Utils::StrongMemoize + include ExclusiveLeaseGuard + + # used by ExclusiveLeaseGuard + DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze + + # From https://salsa.debian.org/ftp-team/dak/-/blob/991aaa27a7f7aa773bb9c0cf2d516e383d9cffa0/setup/core-init.d/080_metadatakeys#L9 + BINARIES_METADATA = %w( + Package + Source + Binary + Version + Essential + Installed-Size + Maintainer + Uploaders + Original-Maintainer + Build-Depends + Build-Depends-Indep + Build-Conflicts + Build-Conflicts-Indep + Architecture + Standards-Version + Format + Files + Dm-Upload-Allowed + Vcs-Browse + Vcs-Hg + Vcs-Darcs + Vcs-Svn + Vcs-Git + Vcs-Browser + Vcs-Arch + Vcs-Bzr + Vcs-Mtn + Vcs-Cvs + Checksums-Sha256 + Checksums-Sha1 + Replaces + Provides + Depends + Pre-Depends + Recommends + Suggests + Enhances + Conflicts + Breaks + Description + Origin + Bugs + Multi-Arch + Homepage + Tag + Package-Type + Installer-Menu-Item + ).freeze + + def initialize(distribution) + @distribution = distribution + @last_generated_at = nil + @md5sum = [] + @sha256 = [] + end + + def execute + try_obtain_lease do + @distribution.transaction do + @last_generated_at = @distribution.component_files.maximum(:created_at) + generate_component_files + generate_release + destroy_old_component_files + end + end + end + + private + + def generate_component_files + @distribution.components.ordered_by_name.each do |component| + @distribution.architectures.ordered_by_name.each do |architecture| + generate_component_file(component, :packages, architecture, :deb) + end + end + end + + def generate_component_file(component, component_file_type, architecture, package_file_type) + paragraphs = @distribution.package_files + .preload_debian_file_metadata + .with_debian_component_name(component.name) + .with_debian_architecture_name(architecture.name) + .with_debian_file_type(package_file_type) + .find_each + .map(&method(:package_stanza_from_fields)) + create_component_file(component, component_file_type, architecture, package_file_type, paragraphs.join("\n")) + end + + def package_stanza_from_fields(package_file) + [ + BINARIES_METADATA.map do |metadata_key| + rfc822_field(metadata_key, package_file.debian_fields[metadata_key]) + end, + rfc822_field('Section', package_file.debian_fields['Section'] || 'misc'), + rfc822_field('Priority', package_file.debian_fields['Priority'] || 'extra'), + rfc822_field('Filename', package_filename(package_file)), + rfc822_field('Size', package_file.size), + rfc822_field('MD5sum', package_file.file_md5), + rfc822_field('SHA256', package_file.file_sha256) + ].flatten.compact.join('') + end + + def package_filename(package_file) + letter = package_file.package.name.start_with?('lib') ? package_file.package.name[0..3] : package_file.package.name[0] + "#{pool_prefix(package_file)}/#{letter}/#{package_file.package.name}/#{package_file.file_name}" + end + + def pool_prefix(package_file) + case @distribution + when ::Packages::Debian::GroupDistribution + "pool/#{@distribution.codename}/#{package_file.package.project_id}" + else + "pool/#{@distribution.codename}/#{@distribution.container_id}" + end + end + + def create_component_file(component, component_file_type, architecture, package_file_type, content) + component_file = component.files.create!( + file_type: component_file_type, + architecture: architecture, + compression_type: nil, + file: CarrierWaveStringFile.new(content), + file_md5: Digest::MD5.hexdigest(content), + file_sha256: Digest::SHA256.hexdigest(content) + ) + @md5sum.append(" #{component_file.file_md5} #{component_file.size.to_s.rjust(8)} #{component_file.relative_path}") + @sha256.append(" #{component_file.file_sha256} #{component_file.size.to_s.rjust(8)} #{component_file.relative_path}") + end + + def generate_release + @distribution.file = CarrierWaveStringFile.new(release_header + release_sums) + @distribution.updated_at = release_date + @distribution.save! + end + + def release_header + strong_memoize(:release_header) do + [ + %w[origin label suite version codename].map do |attribute| + rfc822_field(attribute.capitalize, @distribution.attributes[attribute]) + end, + rfc822_field('Date', release_date.to_formatted_s(:rfc822)), + valid_until_field, + rfc822_field('NotAutomatic', !@distribution.automatic, !@distribution.automatic), + rfc822_field('ButAutomaticUpgrades', @distribution.automatic_upgrades, !@distribution.automatic && @distribution.automatic_upgrades), + rfc822_field('Architectures', @distribution.architectures.map { |architecture| architecture.name }.sort.join(' ')), + rfc822_field('Components', @distribution.components.map { |component| component.name }.sort.join(' ')), + rfc822_field('Description', @distribution.description) + ].flatten.compact.join('') + end + end + + def release_date + strong_memoize(:release_date) do + Time.now.utc + end + end + + def release_sums + ["MD5Sum:", @md5sum, "SHA256:", @sha256].flatten.compact.join("\n") + "\n" + end + + def rfc822_field(name, value, condition = true) + return unless condition + return if value.blank? + + "#{name}: #{value.to_s.gsub("\n\n", "\n.\n").gsub("\n", "\n ")}\n" + end + + def valid_until_field + return unless @distribution.valid_time_duration_seconds + + rfc822_field('Valid-Until', release_date.since(@distribution.valid_time_duration_seconds).to_formatted_s(:rfc822)) + end + + def destroy_old_component_files + # Only keep the last generation and one hour before + return if @last_generated_at.nil? + + @distribution.component_files.created_before(@last_generated_at - 1.hour).destroy_all # rubocop:disable Cop/DestroyAll + end + + # used by ExclusiveLeaseGuard + def lease_key + "packages:debian:generate_distribution_service:distribution:#{@distribution.id}" + end + + # used by ExclusiveLeaseGuard + def lease_timeout + DEFAULT_LEASE_TIMEOUT + end + end + end +end diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 5da3a94c44b..45488791272 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -61,21 +61,21 @@ %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @requesters.count .tab-content #tab-members.tab-pane{ class: ('active' unless invited_active) } - .js-group-members-list{ data: group_members_list_data_attributes(@group, @members, { param_name: :page, params: { invited_members_page: nil, search_invited: nil } }) } + .js-group-members-list{ data: { members_data: group_members_list_data_json(@group, @members, { param_name: :page, params: { invited_members_page: nil, search_invited: nil } }) } } .loading .gl-spinner.gl-spinner-md - if @group.shared_with_group_links.present? #tab-groups.tab-pane - .js-group-group-links-list{ data: group_group_links_list_data_attributes(@group) } + .js-group-group-links-list{ data: { members_data: group_group_links_list_data_json(@group) } } .loading .gl-spinner.gl-spinner-md - if show_invited_members #tab-invited-members.tab-pane{ class: ('active' if invited_active) } - .js-group-invited-members-list{ data: group_members_list_data_attributes(@group, @invited_members, { param_name: :invited_members_page, params: { page: nil } }) } + .js-group-invited-members-list{ data: { members_data: group_members_list_data_json(@group, @invited_members, { param_name: :invited_members_page, params: { page: nil } }) } } .loading .gl-spinner.gl-spinner-md - if show_access_requests #tab-access-requests.tab-pane - .js-group-access-requests-list{ data: group_members_list_data_attributes(@group, @requesters) } + .js-group-access-requests-list{ data: { members_data: group_members_list_data_json(@group, @requesters) } } .loading .gl-spinner.gl-spinner-md diff --git a/app/views/projects/_fork_suggestion.html.haml b/app/views/projects/_fork_suggestion.html.haml index 9888ce417f8..55e609c0ffb 100644 --- a/app/views/projects/_fork_suggestion.html.haml +++ b/app/views/projects/_fork_suggestion.html.haml @@ -1,10 +1,7 @@ +- message_base = s_("ForkSuggestion|You can’t %{edit_start}edit%{edit_end} files directly in this project. Fork this project and submit a merge request with your changes.").html_safe +- message = message_base.html_safe % { edit_start: '<span class="js-file-fork-suggestion-section-action">'.html_safe, edit_end: '</span>'.html_safe } .js-file-fork-suggestion-section.file-fork-suggestion.hidden - %span.file-fork-suggestion-note - You're not allowed to - %span.js-file-fork-suggestion-section-action - edit - files in this project directly. Please fork this project, - make your changes there, and submit a merge request. - = link_to 'Fork', nil, method: :post, class: 'js-fork-suggestion-button gl-button btn btn-grouped btn-confirm-secondary' + %span.file-fork-suggestion-note= message + = link_to s_('ForkSuggestion|Fork'), nil, method: :post, class: 'js-fork-suggestion-button gl-button btn btn-grouped btn-confirm-secondary' %button.js-cancel-fork-suggestion-button.gl-button.btn.btn-grouped{ type: 'button' } - Cancel + = s_('ForkSuggestion|Cancel') diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index e06e278f2e1..0fa9fb7079b 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -82,21 +82,21 @@ %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm= @requesters.count .tab-content #tab-members.tab-pane{ class: ('active' unless groups_tab_active?) } - .js-project-members-list{ data: project_members_list_data_attributes(@project, @project_members, { param_name: :page, params: { search_groups: nil } }) } + .js-project-members-list{ data: { members_data: project_members_list_data_json(@project, @project_members, { param_name: :page, params: { search_groups: nil } }) } } .loading .gl-spinner.gl-spinner-md - if show_groups?(@group_links) #tab-groups.tab-pane{ class: ('active' if groups_tab_active?) } - .js-project-group-links-list{ data: project_group_links_list_data_attributes(@project, @group_links) } + .js-project-group-links-list{ data: { members_data: project_group_links_list_data_json(@project, @group_links) } } .loading .gl-spinner.gl-spinner-md - if show_invited_members?(@project, @invited_members) #tab-invited-members.tab-pane - .js-project-invited-members-list{ data: project_members_list_data_attributes(@project, @invited_members) } + .js-project-invited-members-list{ data: { members_data: project_members_list_data_json(@project, @invited_members) } } .loading .gl-spinner.gl-spinner-md - if show_access_requests?(@project, @requesters) #tab-access-requests.tab-pane - .js-project-access-requests-list{ data: project_members_list_data_attributes(@project, @requesters) } + .js-project-access-requests-list{ data: { members_data: project_members_list_data_json(@project, @requesters) } } .loading .gl-spinner.gl-spinner-md diff --git a/app/views/shared/_confirm_fork_modal.html.haml b/app/views/shared/_confirm_fork_modal.html.haml index 265396d3d8b..ed52aa01047 100644 --- a/app/views/shared/_confirm_fork_modal.html.haml +++ b/app/views/shared/_confirm_fork_modal.html.haml @@ -6,7 +6,7 @@ %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') } %span{ "aria-hidden": true } × .modal-body.p-3 - %p= _("You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.") % { tag_start: '', tag_end: ''} + %p= _("You can’t %{tag_start}edit%{tag_end} files directly in this project. Fork this project and submit a merge request with your changes.") % { tag_start: '', tag_end: ''} .modal-footer = link_to _('Cancel'), '#', class: "btn gl-button btn-default", "data-dismiss" => "modal" = link_to _('Fork project'), fork_path, class: 'btn gl-button btn-confirm', data: { qa_selector: 'fork_project_button' }, method: :post diff --git a/app/workers/update_highest_role_worker.rb b/app/workers/update_highest_role_worker.rb index b818e8177cc..cecf3f99b50 100644 --- a/app/workers/update_highest_role_worker.rb +++ b/app/workers/update_highest_role_worker.rb @@ -17,7 +17,7 @@ class UpdateHighestRoleWorker return unless user.present? - if user.active? && user.user_type.nil? && !user.internal? + if user.active? && user.human? && !user.internal? Users::UpdateHighestMemberRoleService.new(user).execute else UserHighestRole.where(user_id: user_id).delete_all diff --git a/changelogs/unreleased/323195-add-more-blob-attributes.yml b/changelogs/unreleased/323195-add-more-blob-attributes.yml new file mode 100644 index 00000000000..83ddedd47f9 --- /dev/null +++ b/changelogs/unreleased/323195-add-more-blob-attributes.yml @@ -0,0 +1,5 @@ +--- +title: Add more attributes to the blob GraphQL API +merge_request: 61155 +author: +type: added diff --git a/changelogs/unreleased/323477-add-gradle-kotlin-installations-commands.yml b/changelogs/unreleased/323477-add-gradle-kotlin-installations-commands.yml new file mode 100644 index 00000000000..0ce4af9331b --- /dev/null +++ b/changelogs/unreleased/323477-add-gradle-kotlin-installations-commands.yml @@ -0,0 +1,5 @@ +--- +title: Add Gradle Kotlin installations commands +merge_request: 60097 +author: Cromefire_ (@cromefire_) +type: changed diff --git a/changelogs/unreleased/326025-aqualls-fork-standardization.yml b/changelogs/unreleased/326025-aqualls-fork-standardization.yml new file mode 100644 index 00000000000..fd217373bde --- /dev/null +++ b/changelogs/unreleased/326025-aqualls-fork-standardization.yml @@ -0,0 +1,5 @@ +--- +title: Update messages when user cannot directly push code to project +merge_request: 61071 +author: +type: other diff --git a/changelogs/unreleased/kassio-github-importer-null-review-author-bug.yml b/changelogs/unreleased/kassio-github-importer-null-review-author-bug.yml new file mode 100644 index 00000000000..1046d5f3b18 --- /dev/null +++ b/changelogs/unreleased/kassio-github-importer-null-review-author-bug.yml @@ -0,0 +1,5 @@ +--- +title: 'GithubImport: Fix Review importer when the author does not exist anymore' +merge_request: 61257 +author: +type: fixed diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 00000000000..b40240bcc54 --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,2 @@ +# This file is created only to be able to run `derailed exec perf:mem` task +# This task loads the whole Rails application using its own initializers diff --git a/doc/administration/external_pipeline_validation.md b/doc/administration/external_pipeline_validation.md index 2c26e356552..89543e446ac 100644 --- a/doc/administration/external_pipeline_validation.md +++ b/doc/administration/external_pipeline_validation.md @@ -5,31 +5,28 @@ info: To determine the technical writer assigned to the Stage/Group associated w type: reference, howto --- -# External Pipeline Validation +# External pipeline validation You can use an external service to validate a pipeline before it's created. WARNING: This is an experimental feature and subject to change without notice. -## Usage - GitLab sends a POST request to the external service URL with the pipeline -data as payload. GitLab then invalidates the pipeline based on the response -code. If there's an error or the request times out, the pipeline is not -invalidated. - -Response codes: +data as payload. The response code from the external service determines if GitLab +should accept or reject the pipeline. If the response is: -- `200`: Accepted -- `4XX`: Rejected -- All other codes: accepted and logged +- `200`, the pipeline is accepted. +- `4XX`, the pipeline is rejected. +- Other codes, the pipeline is accepted and logged. -### Service Result +If there's an error or the request times out, the pipeline is accepted. -Pipelines rejected by the external validation service aren't created or visible in pipeline lists, in either the GitLab user interface or API. Creating an unaccepted pipeline when using the GitLab user interface displays an error message that states: `Pipeline cannot be run. External validation failed` +Pipelines rejected by the external validation service aren't created, and don't +appear in pipeline lists in the GitLab UI or API. If you create a pipeline in the +UI that is rejected, `Pipeline cannot be run. External validation failed` is displayed. -## Configuration +## Configure external pipeline validation To configure external pipeline validation, add the [`EXTERNAL_VALIDATION_SERVICE_URL` environment variable](environment_variables.md) @@ -39,7 +36,7 @@ By default, requests to the external service time out after five seconds. To ove the default, set the `EXTERNAL_VALIDATION_SERVICE_TIMEOUT` environment variable to the required number of seconds. -## Payload Schema +## Payload schema ```json { diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 6aa1c50ded1..30aa9abbf31 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -11831,9 +11831,14 @@ Returns [`Tree`](#tree). | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="repositoryblobcanmodifyblob"></a>`canModifyBlob` | [`Boolean`](#boolean) | Whether the current user can modify the blob. | | <a id="repositoryblobeditblobpath"></a>`editBlobPath` | [`String`](#string) | Web path to edit the blob in the old-style editor. | +| <a id="repositoryblobexternalstorageurl"></a>`externalStorageUrl` | [`String`](#string) | Web path to download the raw blob via external storage, if enabled. | | <a id="repositoryblobfiletype"></a>`fileType` | [`String`](#string) | The expected format of the blob based on the extension. | +| <a id="repositoryblobforkandeditpath"></a>`forkAndEditPath` | [`String`](#string) | Web path to edit this blob using a forked project. | | <a id="repositoryblobid"></a>`id` | [`ID!`](#id) | ID of the blob. | +| <a id="repositoryblobideeditpath"></a>`ideEditPath` | [`String`](#string) | Web path to edit this blob in the Web IDE. | +| <a id="repositoryblobideforkandeditpath"></a>`ideForkAndEditPath` | [`String`](#string) | Web path to edit this blob in the Web IDE using a forked project. | | <a id="repositorybloblfsoid"></a>`lfsOid` | [`String`](#string) | LFS OID of the blob. | | <a id="repositoryblobmode"></a>`mode` | [`String`](#string) | Blob mode. | | <a id="repositoryblobname"></a>`name` | [`String`](#string) | Blob name. | diff --git a/lib/after_commit_queue.rb b/lib/after_commit_queue.rb index 6a180fdf338..aea4231205d 100644 --- a/lib/after_commit_queue.rb +++ b/lib/after_commit_queue.rb @@ -16,7 +16,7 @@ module AfterCommitQueue def run_after_commit_or_now(&block) if Gitlab::Database.inside_transaction? - if ActiveRecord::Base.connection.current_transaction.records.include?(self) + if ActiveRecord::Base.connection.current_transaction.records&.include?(self) run_after_commit(&block) else # If the current transaction does not include this record, we can run diff --git a/lib/gitlab/ci/pipeline/chain/helpers.rb b/lib/gitlab/ci/pipeline/chain/helpers.rb index 9988b6f18ed..09158bf8bfd 100644 --- a/lib/gitlab/ci/pipeline/chain/helpers.rb +++ b/lib/gitlab/ci/pipeline/chain/helpers.rb @@ -19,6 +19,8 @@ module Gitlab # polluted with other unrelated errors (e.g. state machine) # https://gitlab.com/gitlab-org/gitlab/-/issues/220823 pipeline.errors.add(:base, message) + + pipeline.errors.full_messages end def warning(message) diff --git a/lib/gitlab/github_import/importer/pull_request_review_importer.rb b/lib/gitlab/github_import/importer/pull_request_review_importer.rb index 9f495913897..f476ee13392 100644 --- a/lib/gitlab/github_import/importer/pull_request_review_importer.rb +++ b/lib/gitlab/github_import/importer/pull_request_review_importer.rb @@ -36,12 +36,12 @@ module Gitlab def add_complementary_review_note!(author_id) return if review.note.empty? && !review.approval? - note = "*Created by %{login}*\n\n%{note}" % { - note: review_note_content, - login: review.author.login - } + note_body = MarkdownText.format( + review_note_content, + review.author + ) - add_note!(author_id, note) + add_note!(author_id, note_body) end def review_note_content diff --git a/lib/gitlab/github_import/markdown_text.rb b/lib/gitlab/github_import/markdown_text.rb index b25c4f7becf..fbd54c4d4da 100644 --- a/lib/gitlab/github_import/markdown_text.rb +++ b/lib/gitlab/github_import/markdown_text.rb @@ -19,10 +19,10 @@ module Gitlab end def to_s - if exists - text - else + if author&.login.present? && !exists "*Created by: #{author.login}*\n\n#{text}" + else + text end end end diff --git a/lib/gitlab/github_import/user_finder.rb b/lib/gitlab/github_import/user_finder.rb index 34d1231b9a5..8d584415202 100644 --- a/lib/gitlab/github_import/user_finder.rb +++ b/lib/gitlab/github_import/user_finder.rb @@ -63,7 +63,7 @@ module Gitlab # # user - An instance of `Gitlab::GithubImport::Representation::User`. def user_id_for(user) - find(user.id, user.login) + find(user.id, user.login) if user.present? end # Returns the GitLab ID for the given GitHub ID or username. diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 1c314d85098..7a5b29d6de1 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -607,7 +607,7 @@ module Gitlab unique_users_all_imports: unique_users_all_imports(time_period), bulk_imports: { gitlab: DEPRECATED_VALUE, - gitlab_v1: count(::BulkImport.where(time_period, source_type: :gitlab)) + gitlab_v1: count(::BulkImport.where(**time_period, source_type: :gitlab)) }, project_imports: project_imports(time_period), issue_imports: issue_imports(time_period), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index dd1195bebc3..8cbfb94867c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14265,6 +14265,15 @@ msgstr "" msgid "ForkProject|Want to house several dependent projects under the same namespace?" msgstr "" +msgid "ForkSuggestion|Cancel" +msgstr "" + +msgid "ForkSuggestion|Fork" +msgstr "" + +msgid "ForkSuggestion|You can’t %{edit_start}edit%{edit_end} files directly in this project. Fork this project and submit a merge request with your changes." +msgstr "" + msgid "ForkedFromProjectPath|Forked from" msgstr "" @@ -23200,6 +23209,9 @@ msgstr "" msgid "PackageRegistry|Add Gradle Groovy DSL repository command" msgstr "" +msgid "PackageRegistry|Add Gradle Kotlin DSL repository command" +msgstr "" + msgid "PackageRegistry|Add NuGet Source" msgstr "" @@ -23242,6 +23254,9 @@ msgstr "" msgid "PackageRegistry|Copy Gradle Groovy DSL install command" msgstr "" +msgid "PackageRegistry|Copy Gradle Kotlin DSL install command" +msgstr "" + msgid "PackageRegistry|Copy Maven XML" msgstr "" @@ -23263,6 +23278,9 @@ msgstr "" msgid "PackageRegistry|Copy add Gradle Groovy DSL repository command" msgstr "" +msgid "PackageRegistry|Copy add Gradle Kotlin DSL repository command" +msgstr "" + msgid "PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block." msgstr "" @@ -23314,9 +23332,18 @@ msgstr "" msgid "PackageRegistry|GitLab Packages allows organizations to utilize GitLab as a private repository for a variety of common package formats. %{linkStart}More Information%{linkEnd}" msgstr "" +msgid "PackageRegistry|Gradle Groovy DSL" +msgstr "" + msgid "PackageRegistry|Gradle Groovy DSL install command" msgstr "" +msgid "PackageRegistry|Gradle Kotlin DSL" +msgstr "" + +msgid "PackageRegistry|Gradle Kotlin DSL install command" +msgstr "" + msgid "PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file." msgstr "" @@ -23341,6 +23368,9 @@ msgstr "" msgid "PackageRegistry|Maven Command" msgstr "" +msgid "PackageRegistry|Maven XML" +msgstr "" + msgid "PackageRegistry|NuGet" msgstr "" @@ -23398,12 +23428,6 @@ msgstr "" msgid "PackageRegistry|Show Conan commands" msgstr "" -msgid "PackageRegistry|Show Gradle Groovy DSL commands" -msgstr "" - -msgid "PackageRegistry|Show Maven commands" -msgstr "" - msgid "PackageRegistry|Show NPM commands" msgstr "" @@ -36135,16 +36159,16 @@ msgstr "" msgid "WebIDE|This project does not accept unsigned commits." msgstr "" -msgid "WebIDE|This project does not accept unsigned commits. You will not be able to commit your changes through the Web IDE." +msgid "WebIDE|This project does not accept unsigned commits. You can’t commit changes through the Web IDE." msgstr "" -msgid "WebIDE|You need permission to edit files directly in this project." +msgid "WebIDE|You can’t edit files directly in this project. Fork this project and submit a merge request with your changes." msgstr "" -msgid "WebIDE|You need permission to edit files directly in this project. Fork this project to make your changes and submit a merge request." +msgid "WebIDE|You can’t edit files directly in this project. Go to your fork and submit a merge request with your changes." msgstr "" -msgid "WebIDE|You need permission to edit files directly in this project. Go to your fork to make changes and submit a merge request." +msgid "WebIDE|You need permission to edit files directly in this project." msgstr "" msgid "WebexTeamsService|Send notifications about project events to Webex Teams." @@ -36959,6 +36983,9 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You can’t %{tag_start}edit%{tag_end} files directly in this project. Fork this project and submit a merge request with your changes." +msgstr "" + msgid "You could not create a new trigger." msgstr "" diff --git a/package.json b/package.json index 47dce38a769..087fd21e488 100644 --- a/package.json +++ b/package.json @@ -59,28 +59,28 @@ "@rails/ujs": "^6.0.3-4", "@sentry/browser": "^5.22.3", "@sourcegraph/code-host-integration": "0.0.57", - "@tiptap/core": "^2.0.0-beta.38", - "@tiptap/extension-blockquote": "^2.0.0-beta.6", - "@tiptap/extension-bold": "^2.0.0-beta.6", - "@tiptap/extension-bullet-list": "^2.0.0-beta.6", - "@tiptap/extension-code": "^2.0.0-beta.6", - "@tiptap/extension-code-block-lowlight": "^2.0.0-beta.9", - "@tiptap/extension-document": "^2.0.0-beta.5", - "@tiptap/extension-dropcursor": "^2.0.0-beta.6", - "@tiptap/extension-gapcursor": "^2.0.0-beta.10", - "@tiptap/extension-hard-break": "^2.0.0-beta.6", - "@tiptap/extension-heading": "^2.0.0-beta.6", - "@tiptap/extension-history": "^2.0.0-beta.5", - "@tiptap/extension-horizontal-rule": "^2.0.0-beta.7", - "@tiptap/extension-image": "^2.0.0-beta.4", - "@tiptap/extension-italic": "^2.0.0-beta.6", - "@tiptap/extension-link": "^2.0.0-beta.6", - "@tiptap/extension-list-item": "^2.0.0-beta.6", - "@tiptap/extension-ordered-list": "^2.0.0-beta.6", - "@tiptap/extension-paragraph": "^2.0.0-beta.7", - "@tiptap/extension-strike": "^2.0.0-beta.7", - "@tiptap/extension-text": "^2.0.0-beta.5", - "@tiptap/vue-2": "^2.0.0-beta.21", + "@tiptap/core": "^2.0.0-beta.54", + "@tiptap/extension-blockquote": "^2.0.0-beta.11", + "@tiptap/extension-bold": "^2.0.0-beta.11", + "@tiptap/extension-bullet-list": "^2.0.0-beta.11", + "@tiptap/extension-code": "^2.0.0-beta.11", + "@tiptap/extension-code-block-lowlight": "^2.0.0-beta.18", + "@tiptap/extension-document": "^2.0.0-beta.10", + "@tiptap/extension-dropcursor": "^2.0.0-beta.11", + "@tiptap/extension-gapcursor": "^2.0.0-beta.15", + "@tiptap/extension-hard-break": "^2.0.0-beta.11", + "@tiptap/extension-heading": "^2.0.0-beta.11", + "@tiptap/extension-history": "^2.0.0-beta.10", + "@tiptap/extension-horizontal-rule": "^2.0.0-beta.14", + "@tiptap/extension-image": "^2.0.0-beta.11", + "@tiptap/extension-italic": "^2.0.0-beta.11", + "@tiptap/extension-link": "^2.0.0-beta.15", + "@tiptap/extension-list-item": "^2.0.0-beta.11", + "@tiptap/extension-ordered-list": "^2.0.0-beta.11", + "@tiptap/extension-paragraph": "^2.0.0-beta.12", + "@tiptap/extension-strike": "^2.0.0-beta.12", + "@tiptap/extension-text": "^2.0.0-beta.10", + "@tiptap/vue-2": "^2.0.0-beta.27", "@toast-ui/editor": "^2.5.2", "@toast-ui/vue-editor": "^2.5.2", "apollo-cache-inmemory": "^1.6.6", diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index c18ff9ddbbc..453cc14c267 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -131,8 +131,8 @@ RSpec.describe 'Projects > Files > User edits files', :js do expect(page).to have_selector(:link_or_button, 'Fork') expect(page).to have_selector(:link_or_button, 'Cancel') expect(page).to have_content( - "You're not allowed to edit files in this project directly. "\ - "Please fork this project, make your changes there, and submit a merge request." + "You can’t edit files directly in this project. "\ + "Fork this project and submit a merge request with your changes." ) end diff --git a/spec/features/users/add_email_to_existing_account_spec.rb b/spec/features/users/add_email_to_existing_account_spec.rb index 9130b96b0e3..cf78fc4587f 100644 --- a/spec/features/users/add_email_to_existing_account_spec.rb +++ b/spec/features/users/add_email_to_existing_account_spec.rb @@ -4,13 +4,25 @@ require 'spec_helper' RSpec.describe 'AdditionalEmailToExistingAccount' do describe 'add secondary email associated with account' do - let(:user) { create(:user) } + let_it_be(:user) { create(:user) } + let_it_be(:email) { create(:email, user: user) } - it 'verifies confirmation of additional email' do + before do sign_in(user) + end + + it 'verifies confirmation of additional email' do + visit email_confirmation_path(confirmation_token: email.confirmation_token) + + expect(page).to have_content 'Your email address has been successfully confirmed.' + end + + it 'accepts any pending invites for an email confirmation' do + member = create(:group_member, :invited, invite_email: email.email) - email = create(:email, user: user) visit email_confirmation_path(confirmation_token: email.confirmation_token) + + expect(member.reload.user).to eq(user) expect(page).to have_content 'Your email address has been successfully confirmed.' end end diff --git a/spec/frontend/issues_list/mock_data.js b/spec/frontend/issues_list/mock_data.js index eede806c42f..d6a23c4dcff 100644 --- a/spec/frontend/issues_list/mock_data.js +++ b/spec/frontend/issues_list/mock_data.js @@ -16,6 +16,8 @@ export const locationSearch = [ 'confidential=no', 'iteration_title=season:+%234', 'not[iteration_title]=season:+%2320', + 'epic_id=12', + 'not[epic_id]=34', 'weight=1', 'not[weight]=3', ].join('&'); @@ -24,6 +26,7 @@ export const locationSearchWithSpecialValues = [ 'assignee_id=None', 'my_reaction_emoji=None', 'iteration_id=Current', + 'epic_id=None', 'weight=None', ].join('&'); @@ -42,6 +45,8 @@ export const filteredTokens = [ { type: 'confidential', value: { data: 'no', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: 'season: #4', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: 'season: #20', operator: OPERATOR_IS_NOT } }, + { type: 'epic_id', value: { data: '12', operator: OPERATOR_IS } }, + { type: 'epic_id', value: { data: '34', operator: OPERATOR_IS_NOT } }, { type: 'weight', value: { data: '1', operator: OPERATOR_IS } }, { type: 'weight', value: { data: '3', operator: OPERATOR_IS_NOT } }, { type: 'filtered-search-term', value: { data: 'find' } }, @@ -52,6 +57,7 @@ export const filteredTokensWithSpecialValues = [ { type: 'assignee_username', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'my_reaction_emoji', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: 'Current', operator: OPERATOR_IS } }, + { type: 'epic_id', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'weight', value: { data: 'None', operator: OPERATOR_IS } }, ]; @@ -68,6 +74,8 @@ export const apiParams = { confidential: 'no', iteration_title: 'season: #4', 'not[iteration_title]': 'season: #20', + epic_id: '12', + 'not[epic_id]': '34', weight: '1', 'not[weight]': '3', }; @@ -76,6 +84,7 @@ export const apiParamsWithSpecialValues = { assignee_id: 'None', my_reaction_emoji: 'None', iteration_id: 'Current', + epic_id: 'None', weight: 'None', }; @@ -92,6 +101,8 @@ export const urlParams = { confidential: ['no'], iteration_title: ['season: #4'], 'not[iteration_title]': ['season: #20'], + epic_id: ['12'], + 'not[epic_id]': ['34'], weight: ['1'], 'not[weight]': ['3'], }; @@ -100,5 +111,6 @@ export const urlParamsWithSpecialValues = { assignee_id: ['None'], my_reaction_emoji: ['None'], iteration_id: ['Current'], + epic_id: ['None'], weight: ['None'], }; diff --git a/spec/frontend/members/index_spec.js b/spec/frontend/members/index_spec.js index 751c4674a60..b07534ae4ed 100644 --- a/spec/frontend/members/index_spec.js +++ b/spec/frontend/members/index_spec.js @@ -2,7 +2,7 @@ import { createWrapper } from '@vue/test-utils'; import MembersApp from '~/members/components/app.vue'; import { MEMBER_TYPES } from '~/members/constants'; import { initMembersApp } from '~/members/index'; -import { membersJsonString, members, paginationJsonString, pagination } from './mock_data'; +import { members, pagination, dataAttribute } from './mock_data'; describe('initMembersApp', () => { let el; @@ -23,11 +23,7 @@ describe('initMembersApp', () => { beforeEach(() => { el = document.createElement('div'); - el.setAttribute('data-members', membersJsonString); - el.setAttribute('data-pagination', paginationJsonString); - el.setAttribute('data-source-id', '234'); - el.setAttribute('data-can-manage-members', 'true'); - el.setAttribute('data-member-path', '/groups/foo-bar/-/group_members/:id'); + el.setAttribute('data-members-data', dataAttribute); window.gon = { current_user_id: 123 }; }); diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js index 6e1ee979839..d0a7c36349b 100644 --- a/spec/frontend/members/mock_data.js +++ b/spec/frontend/members/mock_data.js @@ -80,13 +80,13 @@ export const inheritedMember = { ...member, isDirectMember: false }; export const member2faEnabled = { ...member, user: { ...member.user, twoFactorEnabled: true } }; -export const paginationJsonString = JSON.stringify({ +export const paginationData = { current_page: 1, per_page: 5, total_items: 10, param_name: 'page', params: { search_groups: null }, -}); +}; export const pagination = { currentPage: 1, @@ -95,3 +95,12 @@ export const pagination = { paramName: 'page', params: { search_groups: null }, }; + +export const dataAttribute = JSON.stringify({ + members, + pagination: paginationData, + source_id: 234, + can_manage_members: true, + member_path: '/groups/foo-bar/-/group_members/:id', + ldap_override_path: '/groups/ldap-group/-/group_members/:id/override', +}); diff --git a/spec/frontend/members/utils_spec.js b/spec/frontend/members/utils_spec.js index 91e99876238..72696979722 100644 --- a/spec/frontend/members/utils_spec.js +++ b/spec/frontend/members/utils_spec.js @@ -20,10 +20,9 @@ import { member2faEnabled, group, invite, - membersJsonString, members, - paginationJsonString, pagination, + dataAttribute, } from './mock_data'; const IS_CURRENT_USER_ID = 123; @@ -260,22 +259,20 @@ describe('Members Utils', () => { beforeEach(() => { el = document.createElement('div'); - el.setAttribute('data-members', membersJsonString); - el.setAttribute('data-pagination', paginationJsonString); - el.setAttribute('data-source-id', '234'); - el.setAttribute('data-can-manage-members', 'true'); + el.setAttribute('data-members-data', dataAttribute); }); afterEach(() => { el = null; }); - it('correctly parses the data attributes', () => { - expect(parseDataAttributes(el)).toEqual({ + it('correctly parses the data attribute', () => { + expect(parseDataAttributes(el)).toMatchObject({ members, pagination, sourceId: 234, canManageMembers: true, + memberPath: '/groups/foo-bar/-/group_members/:id', }); }); }); diff --git a/spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap index a6bb9e868ee..8a2793c0010 100644 --- a/spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap +++ b/spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap @@ -1,16 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MavenInstallation gradle renders all the messages 1`] = ` +exports[`MavenInstallation groovy renders all the messages 1`] = ` <div> <installation-title-stub - options="[object Object],[object Object]" + options="[object Object],[object Object],[object Object]" packagetype="maven" /> <code-instruction-stub class="gl-mb-5" copytext="Copy Gradle Groovy DSL install command" - instruction="foo/gradle/install" + instruction="foo/gradle/groovy/install" label="Gradle Groovy DSL install command" trackingaction="copy_gradle_install_command" trackinglabel="code_instruction" @@ -18,7 +18,7 @@ exports[`MavenInstallation gradle renders all the messages 1`] = ` <code-instruction-stub copytext="Copy add Gradle Groovy DSL repository command" - instruction="foo/gradle/add/source" + instruction="foo/gradle/groovy/add/source" label="Add Gradle Groovy DSL repository command" multiline="true" trackingaction="copy_gradle_add_to_source_command" @@ -27,10 +27,37 @@ exports[`MavenInstallation gradle renders all the messages 1`] = ` </div> `; +exports[`MavenInstallation kotlin renders all the messages 1`] = ` +<div> + <installation-title-stub + options="[object Object],[object Object],[object Object]" + packagetype="maven" + /> + + <code-instruction-stub + class="gl-mb-5" + copytext="Copy Gradle Kotlin DSL install command" + instruction="foo/gradle/kotlin/install" + label="Gradle Kotlin DSL install command" + trackingaction="copy_kotlin_install_command" + trackinglabel="code_instruction" + /> + + <code-instruction-stub + copytext="Copy add Gradle Kotlin DSL repository command" + instruction="foo/gradle/kotlin/add/source" + label="Add Gradle Kotlin DSL repository command" + multiline="true" + trackingaction="copy_kotlin_add_to_source_command" + trackinglabel="code_instruction" + /> +</div> +`; + exports[`MavenInstallation maven renders all the messages 1`] = ` <div> <installation-title-stub - options="[object Object],[object Object]" + options="[object Object],[object Object],[object Object]" packagetype="maven" /> diff --git a/spec/frontend/packages/details/components/maven_installation_spec.js b/spec/frontend/packages/details/components/maven_installation_spec.js index d49a7c0b561..4972fe70a3d 100644 --- a/spec/frontend/packages/details/components/maven_installation_spec.js +++ b/spec/frontend/packages/details/components/maven_installation_spec.js @@ -17,8 +17,10 @@ describe('MavenInstallation', () => { const xmlCodeBlock = 'foo/xml'; const mavenCommandStr = 'foo/command'; const mavenSetupXml = 'foo/setup'; - const gradleGroovyInstallCommandText = 'foo/gradle/install'; - const gradleGroovyAddSourceCommandText = 'foo/gradle/add/source'; + const gradleGroovyInstallCommandText = 'foo/gradle/groovy/install'; + const gradleGroovyAddSourceCommandText = 'foo/gradle/groovy/add/source'; + const gradleKotlinInstallCommandText = 'foo/gradle/kotlin/install'; + const gradleKotlinAddSourceCommandText = 'foo/gradle/kotlin/add/source'; const store = new Vuex.Store({ state: { @@ -31,6 +33,8 @@ describe('MavenInstallation', () => { mavenSetupXml: () => mavenSetupXml, gradleGroovyInstalCommand: () => gradleGroovyInstallCommandText, gradleGroovyAddSourceCommand: () => gradleGroovyAddSourceCommandText, + gradleKotlinInstalCommand: () => gradleKotlinInstallCommandText, + gradleKotlinAddSourceCommand: () => gradleKotlinAddSourceCommandText, }, }); @@ -59,8 +63,9 @@ describe('MavenInstallation', () => { expect(findInstallationTitle().props()).toMatchObject({ packageType: 'maven', options: [ - { value: 'maven', label: 'Show Maven commands' }, - { value: 'groovy', label: 'Show Gradle Groovy DSL commands' }, + { value: 'maven', label: 'Maven XML' }, + { value: 'groovy', label: 'Gradle Groovy DSL' }, + { value: 'kotlin', label: 'Gradle Kotlin DSL' }, ], }); }); @@ -117,9 +122,9 @@ describe('MavenInstallation', () => { }); }); - describe('gradle', () => { + describe('groovy', () => { beforeEach(() => { - createComponent({ data: { instructionType: 'gradle' } }); + createComponent({ data: { instructionType: 'groovy' } }); }); it('renders all the messages', () => { @@ -146,4 +151,34 @@ describe('MavenInstallation', () => { }); }); }); + + describe('kotlin', () => { + beforeEach(() => { + createComponent({ data: { instructionType: 'kotlin' } }); + }); + + it('renders all the messages', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + + describe('installation commands', () => { + it('renders the gradle install command', () => { + expect(findCodeInstructions().at(0).props()).toMatchObject({ + instruction: gradleKotlinInstallCommandText, + multiline: false, + trackingAction: TrackingActions.COPY_KOTLIN_INSTALL_COMMAND, + }); + }); + }); + + describe('setup commands', () => { + it('renders the correct gradle command', () => { + expect(findCodeInstructions().at(1).props()).toMatchObject({ + instruction: gradleKotlinAddSourceCommandText, + multiline: true, + trackingAction: TrackingActions.COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, + }); + }); + }); + }); }); diff --git a/spec/frontend/packages/details/store/getters_spec.js b/spec/frontend/packages/details/store/getters_spec.js index 005adece56e..8210511bf8f 100644 --- a/spec/frontend/packages/details/store/getters_spec.js +++ b/spec/frontend/packages/details/store/getters_spec.js @@ -19,6 +19,8 @@ import { groupExists, gradleGroovyInstalCommand, gradleGroovyAddSourceCommand, + gradleKotlinInstalCommand, + gradleKotlinAddSourceCommand, } from '~/packages/details/store/getters'; import { conanPackage, @@ -259,6 +261,24 @@ describe('Getters PackageDetails Store', () => { }); }); + describe('gradle kotlin string getters', () => { + it('gets the correct gradleKotlinInstalCommand', () => { + setupState(); + + expect(gradleKotlinInstalCommand(state)).toMatchInlineSnapshot( + `"implementation(\\"com.test.app:test-app:1.0-SNAPSHOT\\")"`, + ); + }); + + it('gets the correct gradleKotlinAddSourceCommand', () => { + setupState(); + + expect(gradleKotlinAddSourceCommand(state)).toMatchInlineSnapshot( + `"maven(\\"foo/registry\\")"`, + ); + }); + }); + describe('check if group', () => { it('is set', () => { setupState({ groupListUrl: '/groups/composer/-/packages' }); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js index c49a1ab68b1..3b5d0dba195 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js @@ -139,8 +139,8 @@ export const mockEpicToken = { symbol: '&', token: EpicToken, operators: [{ value: '=', description: 'is', default: 'true' }], + idProperty: 'iid', fetchEpics: () => Promise.resolve({ data: mockEpics }), - fetchSingleEpic: () => Promise.resolve({ data: mockEpics[0] }), }; export const mockReactionEmojiToken = { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js index 0c3f9e1363f..addc058f658 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/epic_token_spec.js @@ -68,21 +68,6 @@ describe('EpicToken', () => { await wrapper.vm.$nextTick(); }); - describe('currentValue', () => { - it.each` - data | id - ${`${mockEpics[0].title}::&${mockEpics[0].iid}`} | ${mockEpics[0].iid} - ${mockEpics[0].iid} | ${mockEpics[0].iid} - ${'foobar'} | ${'foobar'} - `('$data returns $id', async ({ data, id }) => { - wrapper.setProps({ value: { data } }); - - await wrapper.vm.$nextTick(); - - expect(wrapper.vm.currentValue).toBe(id); - }); - }); - describe('activeEpic', () => { it('returns object for currently present `value.data`', async () => { wrapper.setProps({ @@ -140,20 +125,6 @@ describe('EpicToken', () => { expect(wrapper.vm.loading).toBe(false); }); }); - - describe('fetchSingleEpic', () => { - it('calls `config.fetchSingleEpic` with provided iid param', async () => { - jest.spyOn(wrapper.vm.config, 'fetchSingleEpic'); - - wrapper.vm.fetchSingleEpic(1); - - expect(wrapper.vm.config.fetchSingleEpic).toHaveBeenCalledWith(1); - - await waitForPromises(); - - expect(wrapper.vm.epics).toEqual([mockEpics[0]]); - }); - }); }); describe('template', () => { diff --git a/spec/graphql/types/repository/blob_type_spec.rb b/spec/graphql/types/repository/blob_type_spec.rb index c588f8230de..beab4dcebc2 100644 --- a/spec/graphql/types/repository/blob_type_spec.rb +++ b/spec/graphql/types/repository/blob_type_spec.rb @@ -25,7 +25,12 @@ RSpec.describe Types::Repository::BlobType do :replace_path, :simple_viewer, :rich_viewer, - :plain_data + :plain_data, + :can_modify_blob, + :ide_edit_path, + :external_storage_url, + :fork_and_edit_path, + :ide_fork_and_edit_path ) end end diff --git a/spec/helpers/groups/group_members_helper_spec.rb b/spec/helpers/groups/group_members_helper_spec.rb index 19b3ced2304..c3f1509fbc8 100644 --- a/spec/helpers/groups/group_members_helper_spec.rb +++ b/spec/helpers/groups/group_members_helper_spec.rb @@ -23,122 +23,119 @@ RSpec.describe Groups::GroupMembersHelper do end end - describe '#group_group_links_data_json' do - include_context 'group_group_link' + describe '#group_members_list_data_json' do + let(:group_members) { create_list(:group_member, 2, group: group, created_by: current_user) } - it 'matches json schema' do - json = helper.group_group_links_data_json(shared_group.shared_with_group_links) + let(:pagination) { {} } + let(:collection) { group_members } + let(:presented_members) { present_members(collection) } - expect(json).to match_schema('group_link/group_group_links') - end - end + subject { Gitlab::Json.parse(helper.group_members_list_data_json(group, presented_members, pagination)) } - describe '#members_data_json' do shared_examples 'members.json' do - it 'matches json schema' do - json = helper.members_data_json(group, present_members([group_member])) - - expect(json).to match_schema('members') + it 'returns `members` property that matches json schema' do + expect(subject['members'].to_json).to match_schema('members') end end - context 'for a group member' do - let(:group_member) { create(:group_member, group: group, created_by: current_user) } + before do + allow(helper).to receive(:group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id') + allow(helper).to receive(:can?).with(current_user, :admin_group_member, group).and_return(true) + end + + it 'returns expected json' do + expected = { + member_path: '/groups/foo-bar/-/group_members/:id', + source_id: group.id, + can_manage_members: true + }.as_json + expect(subject).to include(expected) + end + + context 'for a group member' do it_behaves_like 'members.json' context 'with user status set' do let(:user) { create(:user) } let!(:status) { create(:user_status, user: user) } - let(:group_member) { create(:group_member, group: group, user: user, created_by: current_user) } + let(:group_members) { [create(:group_member, group: group, user: user, created_by: current_user)] } it_behaves_like 'members.json' end end context 'for an invited group member' do - let(:group_member) { create(:group_member, :invited, group: group, created_by: current_user) } + let(:group_members) { create_list(:group_member, 2, :invited, group: group, created_by: current_user) } it_behaves_like 'members.json' end context 'for an access request' do - let(:group_member) { create(:group_member, :access_request, group: group, created_by: current_user) } + let(:group_members) { create_list(:group_member, 2, :access_request, group: group, created_by: current_user) } it_behaves_like 'members.json' end - end - - describe '#group_members_list_data_attributes' do - let_it_be(:group_members) { create_list(:group_member, 2, group: group, created_by: current_user) } - - before do - allow(helper).to receive(:group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id') - allow(helper).to receive(:can?).with(current_user, :admin_group_member, group).and_return(true) - end - - it 'returns expected hash' do - expect(helper.group_members_list_data_attributes(group, present_members(group_members))).to include({ - members: helper.members_data_json(group, present_members(group_members)), - member_path: '/groups/foo-bar/-/group_members/:id', - source_id: group.id, - can_manage_members: 'true' - }) - end context 'when pagination is not available' do it 'sets `pagination` attribute to expected json' do - expect(helper.group_members_list_data_attributes(group, present_members(group_members))[:pagination]).to match({ + expected = { current_page: nil, per_page: nil, total_items: 2, param_name: nil, params: {} - }.to_json) + }.as_json + + expect(subject['pagination']).to include(expected) end end context 'when pagination is available' do let(:collection) { Kaminari.paginate_array(group_members).page(1).per(1) } + let(:pagination) { { param_name: :page, params: { search_groups: nil } } } it 'sets `pagination` attribute to expected json' do - expect( - helper.group_members_list_data_attributes( - group, - present_members(collection), - { param_name: :page, params: { search_groups: nil } } - )[:pagination] - ).to match({ + expected = { current_page: 1, per_page: 1, total_items: 2, param_name: :page, params: { search_groups: nil } - }.to_json) + }.as_json + + expect(subject['pagination']).to include(expected) end end end - describe '#group_group_links_list_data_attributes' do + describe '#group_group_links_list_data_json' do include_context 'group_group_link' + subject { Gitlab::Json.parse(helper.group_group_links_list_data_json(shared_group)) } + before do allow(helper).to receive(:group_group_link_path).with(shared_group, ':id').and_return('/groups/foo-bar/-/group_links/:id') end - it 'returns expected hash' do - expect(helper.group_group_links_list_data_attributes(shared_group)).to include({ + it 'returns expected json' do + expected = { pagination: { current_page: nil, per_page: nil, total_items: 1, param_name: nil, params: {} - }.to_json, - members: helper.group_group_links_data_json(shared_group.shared_with_group_links), + }, member_path: '/groups/foo-bar/-/group_links/:id', source_id: shared_group.id - }) + }.as_json + + expect(subject).to include(expected) + end + + it 'returns `members` property that matches json schema' do + expect(subject['members'].to_json).to match_schema('group_link/group_group_links') end end end diff --git a/spec/helpers/projects/project_members_helper_spec.rb b/spec/helpers/projects/project_members_helper_spec.rb index 03e67afc372..90035f3e1c5 100644 --- a/spec/helpers/projects/project_members_helper_spec.rb +++ b/spec/helpers/projects/project_members_helper_spec.rb @@ -149,57 +149,61 @@ RSpec.describe Projects::ProjectMembersHelper do describe 'project members' do let_it_be(:project_members) { create_list(:project_member, 2, project: project) } - describe '#project_members_data_json' do - it 'matches json schema' do - expect(helper.project_members_data_json(project, present_members(project_members))).to match_schema('members') - end - end + let(:collection) { project_members } + let(:presented_members) { present_members(collection) } - describe '#project_members_list_data_attributes' do + describe '#project_members_list_data_json' do let(:allow_admin_project) { true } + let(:pagination) { {} } + + subject { Gitlab::Json.parse(helper.project_members_list_data_json(project, presented_members, pagination)) } before do allow(helper).to receive(:project_project_member_path).with(project, ':id').and_return('/foo-bar/-/project_members/:id') end - it 'returns expected hash' do - expect(helper.project_members_list_data_attributes(project, present_members(project_members))).to include({ - members: helper.project_members_data_json(project, present_members(project_members)), + it 'returns expected json' do + expected = { member_path: '/foo-bar/-/project_members/:id', source_id: project.id, - can_manage_members: 'true' - }) + can_manage_members: true + }.as_json + + expect(subject).to include(expected) + end + + it 'returns `members` property that matches json schema' do + expect(subject['members'].to_json).to match_schema('members') end context 'when pagination is not available' do it 'sets `pagination` attribute to expected json' do - expect(helper.project_members_list_data_attributes(project, present_members(project_members))[:pagination]).to match({ + expected = { current_page: nil, per_page: nil, total_items: 2, param_name: nil, params: {} - }.to_json) + }.as_json + + expect(subject['pagination']).to include(expected) end end context 'when pagination is available' do let(:collection) { Kaminari.paginate_array(project_members).page(1).per(1) } + let(:pagination) { { param_name: :page, params: { search_groups: nil } } } it 'sets `pagination` attribute to expected json' do - expect( - helper.project_members_list_data_attributes( - project, - present_members(collection), - { param_name: :page, params: { search_groups: nil } } - )[:pagination] - ).to match({ + expected = { current_page: 1, per_page: 1, total_items: 2, param_name: :page, params: { search_groups: nil } - }.to_json) + }.as_json + + expect(subject['pagination']).to match(expected) end end end @@ -210,32 +214,33 @@ RSpec.describe Projects::ProjectMembersHelper do let(:allow_admin_project) { true } - describe '#project_group_links_data_json' do - it 'matches json schema' do - expect(helper.project_group_links_data_json(project_group_links)).to match_schema('group_link/project_group_links') - end - end + describe '#project_group_links_list_data_json' do + subject { Gitlab::Json.parse(helper.project_group_links_list_data_json(project, project_group_links)) } - describe '#project_group_links_list_data_attributes' do before do allow(helper).to receive(:project_group_link_path).with(project, ':id').and_return('/foo-bar/-/group_links/:id') allow(helper).to receive(:can?).with(current_user, :admin_project_member, project).and_return(true) end - it 'returns expected hash' do - expect(helper.project_group_links_list_data_attributes(project, project_group_links)).to include({ - members: helper.project_group_links_data_json(project_group_links), + it 'returns expected json' do + expected = { pagination: { current_page: nil, per_page: nil, total_items: 1, param_name: nil, params: {} - }.to_json, + }, member_path: '/foo-bar/-/group_links/:id', source_id: project.id, - can_manage_members: 'true' - }) + can_manage_members: true + }.as_json + + expect(subject).to include(expected) + end + + it 'returns `members` property that matches json schema' do + expect(subject['members'].to_json).to match_schema('group_link/project_group_links') end end end diff --git a/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb index 5002e0384f3..fa8b5e6ccf0 100644 --- a/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/pull_request_review_importer_spec.rb @@ -130,7 +130,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean .to change(Note, :count).by(1) last_note = merge_request.notes.last - expect(last_note.note).to eq("*Created by author*\n\n**Review:** Approved") + expect(last_note.note).to eq("*Created by: author*\n\n**Review:** Approved") expect(last_note.author).to eq(project.creator) expect(last_note.created_at).to eq(submitted_at) end @@ -153,6 +153,20 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean end end + context 'when original author was deleted in github' do + let(:review) { create_review(type: 'APPROVED', note: '', author: nil) } + + it 'creates a note for the review without the author information' do + expect { subject.execute } + .to change(Note, :count).by(1) + + last_note = merge_request.notes.last + expect(last_note.note).to eq('**Review:** Approved') + expect(last_note.author).to eq(project.creator) + expect(last_note.created_at).to eq(submitted_at) + end + end + context 'when the review has a note text' do context 'when the review is "APPROVED"' do let(:review) { create_review(type: 'APPROVED') } @@ -163,7 +177,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean last_note = merge_request.notes.last - expect(last_note.note).to eq("*Created by author*\n\n**Review:** Approved\n\nnote") + expect(last_note.note).to eq("*Created by: author*\n\n**Review:** Approved\n\nnote") expect(last_note.author).to eq(project.creator) expect(last_note.created_at).to eq(submitted_at) end @@ -178,7 +192,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean last_note = merge_request.notes.last - expect(last_note.note).to eq("*Created by author*\n\n**Review:** Commented\n\nnote") + expect(last_note.note).to eq("*Created by: author*\n\n**Review:** Commented\n\nnote") expect(last_note.author).to eq(project.creator) expect(last_note.created_at).to eq(submitted_at) end @@ -193,7 +207,7 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean last_note = merge_request.notes.last - expect(last_note.note).to eq("*Created by author*\n\n**Review:** Changes requested\n\nnote") + expect(last_note.note).to eq("*Created by: author*\n\n**Review:** Changes requested\n\nnote") expect(last_note.author).to eq(project.creator) expect(last_note.created_at).to eq(submitted_at) end @@ -201,13 +215,13 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestReviewImporter, :clean end end - def create_review(type:, note: 'note') + def create_review(type:, note: 'note', author: { id: 999, login: 'author' }) Gitlab::GithubImport::Representation::PullRequestReview.from_json_hash( merge_request_id: merge_request.id, review_type: type, note: note, submitted_at: submitted_at.to_s, - author: { id: 999, login: 'author' } + author: author ) end end diff --git a/spec/lib/gitlab/github_import/user_finder_spec.rb b/spec/lib/gitlab/github_import/user_finder_spec.rb index 0dd2bd4df45..20e67a784e1 100644 --- a/spec/lib/gitlab/github_import/user_finder_spec.rb +++ b/spec/lib/gitlab/github_import/user_finder_spec.rb @@ -61,6 +61,10 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache do expect(finder).to receive(:find).with(user.id, user.login).and_return(42) expect(finder.user_id_for(user)).to eq(42) end + + it 'does not fail with empty input' do + expect(finder.user_id_for(nil)).to eq(nil) + end end describe '#find' do diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb index 62f2a53ab3c..cd0938682db 100644 --- a/spec/models/email_spec.rb +++ b/spec/models/email_spec.rb @@ -44,12 +44,11 @@ RSpec.describe Email do end end - describe 'delegation' do - let(:user) { create(:user) } - - it 'delegates to :user' do - expect(build(:email, user: user).username).to eq user.username - end + describe 'delegations' do + it { is_expected.to delegate_method(:can?).to(:user) } + it { is_expected.to delegate_method(:username).to(:user) } + it { is_expected.to delegate_method(:pending_invitations).to(:user) } + it { is_expected.to delegate_method(:accept_pending_invitations!).to(:user) } end describe 'Devise emails' do diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index 981245627af..390d1552c16 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -100,7 +100,8 @@ RSpec.describe InternalId do context 'when executed outside of transaction' do it 'increments counter with in_transaction: "false"' do - expect(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } + allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } + expect(InternalId::InternalIdGenerator.internal_id_transactions_total).to receive(:increment) .with(operation: :generate, usage: 'issues', in_transaction: 'false').and_call_original @@ -158,7 +159,8 @@ RSpec.describe InternalId do let(:value) { 2 } it 'increments counter with in_transaction: "false"' do - expect(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } + allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } + expect(InternalId::InternalIdGenerator.internal_id_transactions_total).to receive(:increment) .with(operation: :reset, usage: 'issues', in_transaction: 'false').and_call_original @@ -228,7 +230,8 @@ RSpec.describe InternalId do context 'when executed outside of transaction' do it 'increments counter with in_transaction: "false"' do - expect(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } + allow(ActiveRecord::Base.connection).to receive(:transaction_open?) { false } + expect(InternalId::InternalIdGenerator.internal_id_transactions_total).to receive(:increment) .with(operation: :track_greatest, usage: 'issues', in_transaction: 'false').and_call_original diff --git a/spec/models/packages/package_file_spec.rb b/spec/models/packages/package_file_spec.rb index 9a08569cadb..f8ddd59ddc8 100644 --- a/spec/models/packages/package_file_spec.rb +++ b/spec/models/packages/package_file_spec.rb @@ -2,6 +2,11 @@ require 'spec_helper' RSpec.describe Packages::PackageFile, type: :model do + let_it_be(:project) { create(:project) } + let_it_be(:package_file1) { create(:package_file, :xml, file_name: 'FooBar') } + let_it_be(:package_file2) { create(:package_file, :xml, file_name: 'ThisIsATest') } + let_it_be(:debian_package) { create(:debian_package, project: project) } + describe 'relationships' do it { is_expected.to belong_to(:package) } it { is_expected.to have_one(:conan_file_metadatum) } @@ -16,9 +21,6 @@ RSpec.describe Packages::PackageFile, type: :model do end context 'with package filenames' do - let_it_be(:package_file1) { create(:package_file, :xml, file_name: 'FooBar') } - let_it_be(:package_file2) { create(:package_file, :xml, file_name: 'ThisIsATest') } - describe '.with_file_name' do let(:filename) { 'FooBar' } @@ -52,6 +54,13 @@ RSpec.describe Packages::PackageFile, type: :model do end end + describe '.for_package_ids' do + it 'returns matching packages' do + expect(described_class.for_package_ids([package_file1.package.id, package_file2.package.id])) + .to contain_exactly(package_file1, package_file2) + end + end + describe '.with_conan_package_reference' do let_it_be(:non_matching_package_file) { create(:package_file, :nuget) } let_it_be(:metadatum) { create(:conan_file_metadatum, :package_file) } @@ -64,7 +73,6 @@ RSpec.describe Packages::PackageFile, type: :model do end describe '.for_rubygem_with_file_name' do - let_it_be(:project) { create(:project) } let_it_be(:non_ruby_package) { create(:nuget_package, project: project, package_type: :nuget) } let_it_be(:ruby_package) { create(:rubygems_package, project: project, package_type: :rubygems) } let_it_be(:file_name) { 'other.gem' } @@ -78,6 +86,36 @@ RSpec.describe Packages::PackageFile, type: :model do end end + context 'Debian scopes' do + let_it_be(:debian_changes) { debian_package.package_files.last } + let_it_be(:debian_deb) { create(:debian_package_file, package: debian_package)} + let_it_be(:debian_udeb) { create(:debian_package_file, :udeb, package: debian_package)} + + let_it_be(:debian_contrib) do + create(:debian_package_file, package: debian_package).tap do |pf| + pf.debian_file_metadatum.update!(component: 'contrib') + end + end + + let_it_be(:debian_mipsel) do + create(:debian_package_file, package: debian_package).tap do |pf| + pf.debian_file_metadatum.update!(architecture: 'mipsel') + end + end + + describe '#with_debian_file_type' do + it { expect(described_class.with_debian_file_type(:changes)).to contain_exactly(debian_changes) } + end + + describe '#with_debian_component_name' do + it { expect(described_class.with_debian_component_name('contrib')).to contain_exactly(debian_contrib) } + end + + describe '#with_debian_architecture_name' do + it { expect(described_class.with_debian_architecture_name('mipsel')).to contain_exactly(debian_mipsel) } + end + end + describe '#update_file_store callback' do let_it_be(:package_file) { build(:package_file, :nuget, size: nil) } diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index 1fdd31b1f92..38bdf3b9364 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -4,11 +4,12 @@ require 'spec_helper' RSpec.describe BlobPresenter do let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.owner } let(:repository) { project.repository } let(:blob) { repository.blob_at('HEAD', 'files/ruby/regex.rb') } - subject(:presenter) { described_class.new(blob) } + subject(:presenter) { described_class.new(blob, current_user: user) } describe '#web_url' do it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") } @@ -30,6 +31,42 @@ RSpec.describe BlobPresenter do it { expect(presenter.replace_path).to eq("/#{project.full_path}/-/create/#{blob.commit_id}/#{blob.path}") } end + describe '#ide_edit_path' do + it { expect(presenter.ide_edit_path).to eq("/-/ide/project/#{project.full_path}/edit/HEAD/-/files/ruby/regex.rb") } + end + + describe '#fork_and_edit_path' do + it 'generates expected URI + query' do + uri = URI.parse(presenter.fork_and_edit_path) + query = Rack::Utils.parse_query(uri.query) + + expect(uri.path).to eq("/#{project.full_path}/-/forks") + expect(query).to include('continue[to]' => presenter.edit_blob_path, 'namespace_key' => user.namespace_id.to_s) + end + + context 'current_user is nil' do + let(:user) { nil } + + it { expect(presenter.fork_and_edit_path).to be_nil } + end + end + + describe '#ide_fork_and_edit_path' do + it 'generates expected URI + query' do + uri = URI.parse(presenter.ide_fork_and_edit_path) + query = Rack::Utils.parse_query(uri.query) + + expect(uri.path).to eq("/#{project.full_path}/-/forks") + expect(query).to include('continue[to]' => presenter.ide_edit_path, 'namespace_key' => user.namespace_id.to_s) + end + + context 'current_user is nil' do + let(:user) { nil } + + it { expect(presenter.ide_fork_and_edit_path).to be_nil } + end + end + context 'given a Gitlab::Graphql::Representation::TreeEntry' do let(:blob) { Gitlab::Graphql::Representation::TreeEntry.new(super(), repository) } diff --git a/spec/requests/api/ci/runner/runners_post_spec.rb b/spec/requests/api/ci/runner/runners_post_spec.rb index 74619cfebea..b38630183f4 100644 --- a/spec/requests/api/ci/runner/runners_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_post_spec.rb @@ -102,7 +102,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do request expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to include('runner_projects' => ['is invalid']) + expect(json_response['message']).to include('runner_projects.base' => ['Maximum number of ci registered project runners (1) exceeded']) expect(project.runners.reload.size).to eq(1) end end @@ -143,7 +143,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do request expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to include('runner_namespaces' => ['is invalid']) + expect(json_response['message']).to include('runner_namespaces.base' => ['Maximum number of ci registered group runners (1) exceeded']) expect(group.runners.reload.size).to eq(1) end end diff --git a/spec/services/packages/debian/generate_distribution_service_spec.rb b/spec/services/packages/debian/generate_distribution_service_spec.rb new file mode 100644 index 00000000000..0547d18c8bc --- /dev/null +++ b/spec/services/packages/debian/generate_distribution_service_spec.rb @@ -0,0 +1,182 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Packages::Debian::GenerateDistributionService do + let_it_be(:group) { create(:group, :public) } + let_it_be(:project) { create(:project, :public, group: group) } + let_it_be(:project_distribution) { create("debian_project_distribution", container: project, codename: 'unstable', valid_time_duration_seconds: 48.hours.to_i) } + + let_it_be(:incoming) { create(:debian_incoming, project: project) } + + before_all do + ::Packages::Debian::ProcessChangesService.new(incoming.package_files.last, nil).execute + end + + let(:service) { described_class.new(distribution) } + + describe '#execute' do + subject { service.execute } + + shared_examples 'Generate Distribution' do |container_type| + context "for #{container_type}" do + if container_type == :group + let_it_be(:container) { group } + let_it_be(:distribution, reload: true) { create('debian_group_distribution', container: group, codename: 'unstable', valid_time_duration_seconds: 48.hours.to_i) } + else + let_it_be(:container) { project } + let_it_be(:distribution, reload: true) { project_distribution } + end + + context 'with components and architectures' do + let_it_be(:component_main ) { create("debian_#{container_type}_component", distribution: distribution, name: 'main') } + let_it_be(:component_contrib) { create("debian_#{container_type}_component", distribution: distribution, name: 'contrib') } + + let_it_be(:architecture_all ) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'all') } + let_it_be(:architecture_amd64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'amd64') } + let_it_be(:architecture_arm64) { create("debian_#{container_type}_architecture", distribution: distribution, name: 'arm64') } + + let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_all, created_at: '2020-01-24T09:00:00.000Z') } # destroyed + let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component_main, architecture: architecture_amd64, created_at: '2020-01-24T10:29:59.000Z') } # destroyed + let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_all, created_at: '2020-01-24T10:30:00.000Z') } # kept + let_it_be(:component_file4) { create("debian_#{container_type}_component_file", component: component_contrib, architecture: architecture_amd64, created_at: '2020-01-24T11:30:00.000Z') } # kept + + def check_component_file(component_name, component_file_type, architecture_name, expected_content) + component_file = distribution + .component_files + .with_component_name(component_name) + .with_file_type(component_file_type) + .with_architecture_name(architecture_name) + .last + + expect(component_file).not_to be_nil + expect(component_file.file.exists?).to eq(!expected_content.nil?) + + unless expected_content.nil? + component_file.file.use_file do |file_path| + expect(File.read(file_path)).to eq(expected_content) + end + end + end + + it 'updates distribution and component files', :aggregate_failures do + travel_to(Time.utc(2020, 01, 25, 15, 17, 18, 123456)) do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + + expect { subject } + .to not_change { Packages::Package.count } + .and not_change { Packages::PackageFile.count } + .and change { distribution.component_files.count }.from(4).to(2 + 6) + + expected_main_amd64_content = <<~EOF + Package: libsample0 + Source: sample + Version: 1.2.3~alpha2 + Installed-Size: 7 + Maintainer: John Doe <john.doe@example.com> + Architecture: amd64 + Description: Some mostly empty lib + Used in GitLab tests. + . + Testing another paragraph. + Multi-Arch: same + Homepage: https://gitlab.com/ + Section: libs + Priority: optional + Filename: pool/unstable/#{project.id}/s/sample/libsample0_1.2.3~alpha2_amd64.deb + Size: 409600 + MD5sum: fb0842b21adc44207996296fe14439dd + SHA256: 1c383a525bfcba619c7305ccd106d61db501a6bbaf0003bf8d0c429fbdb7fcc1 + + Package: sample-dev + Source: sample (1.2.3~alpha2) + Version: 1.2.3~binary + Installed-Size: 7 + Maintainer: John Doe <john.doe@example.com> + Architecture: amd64 + Depends: libsample0 (= 1.2.3~binary) + Description: Some mostly empty developpement files + Used in GitLab tests. + . + Testing another paragraph. + Multi-Arch: same + Homepage: https://gitlab.com/ + Section: libdevel + Priority: optional + Filename: pool/unstable/#{project.id}/s/sample/sample-dev_1.2.3~binary_amd64.deb + Size: 409600 + MD5sum: d2afbd28e4d74430d22f9504e18bfdf5 + SHA256: 9fbeee2191ce4dab5288fad5ecac1bd369f58fef9a992a880eadf0caf25f086d + EOF + + check_component_file('main', :packages, 'all', nil) + check_component_file('main', :packages, 'amd64', expected_main_amd64_content) + check_component_file('main', :packages, 'arm64', nil) + + check_component_file('contrib', :packages, 'all', nil) + check_component_file('contrib', :packages, 'amd64', nil) + check_component_file('contrib', :packages, 'arm64', nil) + + size = expected_main_amd64_content.length + md5sum = Digest::MD5.hexdigest(expected_main_amd64_content) + sha256 = Digest::SHA256.hexdigest(expected_main_amd64_content) + + expected_release_content = <<~EOF + Codename: unstable + Date: Sat, 25 Jan 2020 15:17:18 +0000 + Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 + Architectures: all amd64 arm64 + Components: contrib main + MD5Sum: + d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-all/Packages + d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-amd64/Packages + d41d8cd98f00b204e9800998ecf8427e 0 contrib/binary-arm64/Packages + d41d8cd98f00b204e9800998ecf8427e 0 main/binary-all/Packages + #{md5sum} #{size} main/binary-amd64/Packages + d41d8cd98f00b204e9800998ecf8427e 0 main/binary-arm64/Packages + SHA256: + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-all/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-amd64/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 contrib/binary-arm64/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-all/Packages + #{sha256} #{size} main/binary-amd64/Packages + e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 0 main/binary-arm64/Packages + EOF + + distribution.file.use_file do |file_path| + expect(File.read(file_path)).to eq(expected_release_content) + end + end + end + end + + context 'without components and architectures' do + it 'updates distribution and component files', :aggregate_failures do + travel_to(Time.utc(2020, 01, 25, 15, 17, 18, 123456)) do + expect(Gitlab::ErrorTracking).not_to receive(:log_exception) + + expect { subject } + .to not_change { Packages::Package.count } + .and not_change { Packages::PackageFile.count } + .and not_change { distribution.component_files.count } + + expected_release_content = <<~EOF + Codename: unstable + Date: Sat, 25 Jan 2020 15:17:18 +0000 + Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 + MD5Sum: + SHA256: + EOF + + distribution.file.use_file do |file_path| + expect(File.read(file_path)).to eq(expected_release_content) + end + end + end + end + end + end + + it_behaves_like 'Generate Distribution', :project + it_behaves_like 'Generate Distribution', :group + end +end diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb index b2a7d349ce6..bfcbc6971d4 100644 --- a/spec/services/users/build_service_spec.rb +++ b/spec/services/users/build_service_spec.rb @@ -179,7 +179,7 @@ RSpec.describe Users::BuildService do params.merge!({ user_type: :project_bot }) end - it { expect(user.project_bot?).to be true} + it { expect(user.project_bot?).to be true } end context 'when not a project_bot' do @@ -187,7 +187,7 @@ RSpec.describe Users::BuildService do params.merge!({ user_type: :alert_bot }) end - it { expect(user.user_type).to be nil } + it { expect(user).to be_human } end end diff --git a/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb index fbb94b4f5c1..33a04059491 100644 --- a/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/architecture_shared_examples.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.shared_examples 'Debian Distribution Architecture' do |factory, container, can_freeze| - let_it_be_with_refind(:architecture) { create(factory) } # rubocop:disable Rails/SaveBang - let_it_be(:architecture_same_distribution, freeze: can_freeze) { create(factory, distribution: architecture.distribution) } + let_it_be_with_refind(:architecture) { create(factory, name: 'name1') } + let_it_be(:architecture_same_distribution, freeze: can_freeze) { create(factory, distribution: architecture.distribution, name: 'name2') } let_it_be(:architecture_same_name, freeze: can_freeze) { create(factory, name: architecture.name) } subject { architecture } @@ -30,20 +30,22 @@ RSpec.shared_examples 'Debian Distribution Architecture' do |factory, container, end describe 'scopes' do + describe '.ordered_by_name' do + subject { described_class.with_distribution(architecture.distribution).ordered_by_name } + + it { expect(subject).to match_array([architecture, architecture_same_distribution]) } + end + describe '.with_distribution' do subject { described_class.with_distribution(architecture.distribution) } - it 'does not return other distributions' do - expect(subject.to_a).to match_array([architecture, architecture_same_distribution]) - end + it { expect(subject).to match_array([architecture, architecture_same_distribution]) } end describe '.with_name' do subject { described_class.with_name(architecture.name) } - it 'does not return other distributions' do - expect(subject.to_a).to match_array([architecture, architecture_same_name]) - end + it { expect(subject).to match_array([architecture, architecture_same_name]) } end end end diff --git a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb index 02ced49ee94..e6b16d5881d 100644 --- a/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb +++ b/spec/support/shared_examples/models/packages/debian/component_file_shared_example.rb @@ -114,11 +114,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| subject { described_class.with_container(container2) } it do - queries = ActiveRecord::QueryRecorder.new do - expect(subject.to_a).to contain_exactly(component_file_other_container) - end - - expect(queries.count).to eq(1) + expect(subject.to_a).to contain_exactly(component_file_other_container) end end @@ -126,11 +122,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| subject { described_class.with_codename_or_suite(distribution2.codename) } it do - queries = ActiveRecord::QueryRecorder.new do - expect(subject.to_a).to contain_exactly(component_file_other_container) - end - - expect(queries.count).to eq(1) + expect(subject.to_a).to contain_exactly(component_file_other_container) end end @@ -138,11 +130,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| subject { described_class.with_component_name(component1_2.name) } it do - queries = ActiveRecord::QueryRecorder.new do - expect(subject.to_a).to contain_exactly(component_file_other_component) - end - - expect(queries.count).to eq(1) + expect(subject.to_a).to contain_exactly(component_file_other_component) end end @@ -150,14 +138,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| subject { described_class.with_file_type(:source) } it do - # let_it_be_with_refind triggers a query - component_file_with_file_type_source - - queries = ActiveRecord::QueryRecorder.new do - expect(subject.to_a).to contain_exactly(component_file_with_file_type_source) - end - - expect(queries.count).to eq(1) + expect(subject.to_a).to contain_exactly(component_file_with_file_type_source) end end @@ -165,11 +146,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| subject { described_class.with_architecture_name(architecture1_2.name) } it do - queries = ActiveRecord::QueryRecorder.new do - expect(subject.to_a).to contain_exactly(component_file_other_architecture) - end - - expect(queries.count).to eq(1) + expect(subject.to_a).to contain_exactly(component_file_other_architecture) end end @@ -177,11 +154,7 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| subject { described_class.with_compression_type(:xz) } it do - queries = ActiveRecord::QueryRecorder.new do - expect(subject.to_a).to contain_exactly(component_file_other_compression_type) - end - - expect(queries.count).to eq(1) + expect(subject.to_a).to contain_exactly(component_file_other_compression_type) end end @@ -189,11 +162,19 @@ RSpec.shared_examples 'Debian Component File' do |container_type, can_freeze| subject { described_class.with_file_sha256('other_sha256') } it do - queries = ActiveRecord::QueryRecorder.new do - expect(subject.to_a).to contain_exactly(component_file_other_file_sha256) - end + expect(subject.to_a).to contain_exactly(component_file_other_file_sha256) + end + end + + describe '.created_before' do + let_it_be(:component_file1) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 4.hours.ago) } + let_it_be(:component_file2) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 3.hours.ago) } + let_it_be(:component_file3) { create("debian_#{container_type}_component_file", component: component1_1, architecture: architecture1_1, created_at: 1.hour.ago) } - expect(queries.count).to eq(1) + subject { described_class.created_before(2.hours.ago) } + + it do + expect(subject.to_a).to contain_exactly(component_file1, component_file2) end end end diff --git a/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb index 23e76d32fb0..635d45f40e5 100644 --- a/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/component_shared_examples.rb @@ -3,8 +3,8 @@ require 'spec_helper' RSpec.shared_examples 'Debian Distribution Component' do |factory, container, can_freeze| - let_it_be_with_refind(:component) { create(factory) } # rubocop:disable Rails/SaveBang - let_it_be(:component_same_distribution, freeze: can_freeze) { create(factory, distribution: component.distribution) } + let_it_be_with_refind(:component) { create(factory, name: 'name1') } + let_it_be(:component_same_distribution, freeze: can_freeze) { create(factory, distribution: component.distribution, name: 'name2') } let_it_be(:component_same_name, freeze: can_freeze) { create(factory, name: component.name) } subject { component } @@ -32,6 +32,14 @@ RSpec.shared_examples 'Debian Distribution Component' do |factory, container, ca end describe 'scopes' do + describe '.ordered_by_name' do + subject { described_class.with_distribution(component.distribution).ordered_by_name } + + it 'sorts by name' do + expect(subject.to_a).to eq([component, component_same_distribution]) + end + end + describe '.with_distribution' do subject { described_class.with_distribution(component.distribution) } diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb index 9eacacf725f..8693d6868e9 100644 --- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb @@ -19,11 +19,6 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| it { is_expected.to have_many(:components).class_name("Packages::Debian::#{container.capitalize}Component").inverse_of(:distribution) } it { is_expected.to have_many(:architectures).class_name("Packages::Debian::#{container.capitalize}Architecture").inverse_of(:distribution) } - - if container != :group - it { is_expected.to have_many(:publications).class_name('Packages::Debian::Publication').inverse_of(:distribution).with_foreign_key(:distribution_id) } - it { is_expected.to have_many(:packages).class_name('Packages::Package').through(:publications) } - end end describe 'validations' do @@ -228,4 +223,44 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| end end end + + if container == :project + describe 'project distribution specifics' do + describe 'relationships' do + it { is_expected.to have_many(:publications).class_name('Packages::Debian::Publication').inverse_of(:distribution).with_foreign_key(:distribution_id) } + it { is_expected.to have_many(:packages).class_name('Packages::Package').through(:publications) } + it { is_expected.to have_many(:package_files).class_name('Packages::PackageFile').through(:packages) } + end + end + else + describe 'group distribution specifics' do + let_it_be(:public_project) { create(:project, :public, group: distribution_with_suite.container)} + let_it_be(:public_distribution_with_same_codename) { create(:debian_project_distribution, container: public_project, codename: distribution_with_suite.codename) } + let_it_be(:public_package_with_same_codename) { create(:debian_package, project: public_project, published_in: public_distribution_with_same_codename)} + let_it_be(:public_distribution_with_same_suite) { create(:debian_project_distribution, container: public_project, suite: distribution_with_suite.suite) } + let_it_be(:public_package_with_same_suite) { create(:debian_package, project: public_project, published_in: public_distribution_with_same_suite)} + + let_it_be(:private_project) { create(:project, :private, group: distribution_with_suite.container)} + let_it_be(:private_distribution_with_same_codename) { create(:debian_project_distribution, container: private_project, codename: distribution_with_suite.codename) } + let_it_be(:private_package_with_same_codename) { create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename)} + let_it_be(:private_distribution_with_same_suite) { create(:debian_project_distribution, container: private_project, suite: distribution_with_suite.suite) } + let_it_be(:private_package_with_same_suite) { create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename)} + + describe '#packages' do + subject { distribution_with_suite.packages } + + it 'returns only public packages with same codename' do + expect(subject.to_a).to contain_exactly(public_package_with_same_codename) + end + end + + describe '#package_files' do + subject { distribution_with_suite.package_files } + + it 'returns only files from public packages with same codename' do + expect(subject.to_a).to contain_exactly(*public_package_with_same_codename.package_files) + end + end + end + end end diff --git a/yarn.lock b/yarn.lock index 4684471a671..7583e5c5f4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1296,10 +1296,10 @@ dom-accessibility-api "^0.5.1" pretty-format "^26.4.2" -"@tiptap/core@^2.0.0-beta.38": - version "2.0.0-beta.38" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.38.tgz#b0ded8d6ced0a345cc8739f6d86dec3d96636987" - integrity sha512-5ZbFQjJoyFtpUbjvSxjlb5NB8MIgaP1jNKiceG/aDt0wEf7vLgPalcfu76e+iITTjHU9VruaKINN1T+QA0fIZw== +"@tiptap/core@^2.0.0-beta.54": + version "2.0.0-beta.54" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.54.tgz#3630c78aab0a72cff0ffa5dda7ff7a2623a307e5" + integrity sha512-N89gA7MKxbXXN/aoe9Qp9fyzgg26Sm+7t+/ADB2HqCvVJhN0fQI0MhNdwOTvxHSxo3AASBMgo7Yj1Zw29TfG1Q== dependencies: "@types/prosemirror-commands" "^1.0.4" "@types/prosemirror-inputrules" "^1.0.4" @@ -1312,173 +1312,173 @@ prosemirror-commands "^1.1.7" prosemirror-inputrules "^1.1.3" prosemirror-keymap "^1.1.3" - prosemirror-model "^1.14.0" + prosemirror-model "^1.14.1" prosemirror-schema-list "^1.1.4" prosemirror-state "^1.3.4" prosemirror-transform "^1.3.2" - prosemirror-view "^1.18.2" + prosemirror-view "^1.18.4" -"@tiptap/extension-blockquote@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.6.tgz#7670998307e88a0d0edf59558355a9328a6acec7" - integrity sha512-DOtr1Iy+wdyX2lrSX9KF6BaHvi0Sxg5tWfrAVHxPU7tCfxt33Xp6Ym97fyuZLlwUIbrzsy/DqBkdTYQ5v+CPMA== +"@tiptap/extension-blockquote@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.11.tgz#ca0be20501506a5555f39c4ec61d0fb3fb8a1713" + integrity sha512-lIE+VYm22rtDPe6nCxisn5YfSibP/oJwiaOdcYNVqBHNx49PDyYKqCT4EO7RWBw2CqZ+SuPIabrU0JYkjmHU7g== dependencies: prosemirror-inputrules "^1.1.3" -"@tiptap/extension-bold@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.6.tgz#347ddb7ad726d369337299b3800367815106de7f" - integrity sha512-hFxVQZcXWBCaCTCG3PJONhvTwoVGq/fJAQuPrYlI18z124Rhx6DeBkPG0FSwQgBeuJyezi4Jz61onkc48jwmSA== +"@tiptap/extension-bold@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.11.tgz#d8ac973d2795fc46231e5969d922ce8c3f7404b8" + integrity sha512-ppak6sIYp2amVWwbU714rGrd+uN0jPLrg3RNh3+uZOuFNEeJK9IHuumhVqIiHqAd1aJ/m9M+tz2x5mrk0M/T3g== -"@tiptap/extension-bubble-menu@^2.0.0-beta.9": - version "2.0.0-beta.9" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.9.tgz#bbf6b819c7edd78f1dbc347f3d102f316928c385" - integrity sha512-GGxHwurQ6PediGy2b6q5at73CRznD6M6f1OSSuFVoIm2Q+FQMOECXKqLHpIOuHke6zYJpaAp1SfdX87/Zs5qaQ== +"@tiptap/extension-bubble-menu@^2.0.0-beta.15": + version "2.0.0-beta.15" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.15.tgz#307f94785caa6d57cbc33dcb37128fc9e910e184" + integrity sha512-FP4AbsAu36PgTXrSWqJoMqDKMNtDx8fU0JffcGpegl8lG2JXlxFg/34RUrAwm4wlSJLHzV87Oasgxrk5QI/tsQ== dependencies: prosemirror-state "^1.3.4" - prosemirror-view "^1.18.2" + prosemirror-view "^1.18.4" tippy.js "^6.3.1" -"@tiptap/extension-bullet-list@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.6.tgz#3830f4a6355f84061929085603f5423ae448d346" - integrity sha512-uO2t1CUc2Puq23c06xJvK9Imh895j1fsTJKJfEiSPDVMGrS3+tGOnQ2f9Fc5IOJITZZzFOpKnxRHC9AS5DySmg== +"@tiptap/extension-bullet-list@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.11.tgz#6d3ccc047120bdd7576f0d1d286ae61a03c8b96e" + integrity sha512-2Fq3uoq1tkwghUezuPO/5UcFEZUn7Ksn7CZ9MY0nPGONazxf2oHPbQjjH/jQW4b9H/qsDBVrxgype+qsW9cOkA== dependencies: prosemirror-inputrules "^1.1.3" -"@tiptap/extension-code-block-lowlight@^2.0.0-beta.9": - version "2.0.0-beta.9" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.9.tgz#96095ca9e00553c488e5f10b2eedd904c1d5ed9e" - integrity sha512-EwDcARAxj8ZowpUQlhM/8aGlw+jmOtuov96Wg9KVmLJbUVmhETivhhlD31WVjyFdDB3nOA6bHqygjy+GP1/jAQ== +"@tiptap/extension-code-block-lowlight@^2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.18.tgz#f90e557f0b62761f104937b0d41a4c4dd5569ee8" + integrity sha512-UYtD6PcfbT8VgcU3x4452xpYi0oORx+jKi/eRoxMnCPPFiDUKLkuAmwM5dEcflCsZHaHRDn1c5pfRFQg9X+Y5A== dependencies: - "@tiptap/extension-code-block" "^2.0.0-beta.6" + "@tiptap/extension-code-block" "^2.0.0-beta.13" "@types/lowlight" "^0.0.1" lowlight "^1.20.0" - prosemirror-model "^1.14.0" + prosemirror-model "^1.14.1" prosemirror-state "^1.3.4" - prosemirror-view "^1.18.2" + prosemirror-view "^1.18.4" -"@tiptap/extension-code-block@^2.0.0-beta.6": - version "2.0.0-beta.8" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.8.tgz#f26d8992be6ea78f34a7db52b0a706293d19922d" - integrity sha512-lSeC98qJ8szMUgp/hFZFMqDfV/boGpMN3kek98BR6dCI8QSHvZWpHrQ8n9dyc8NEGAuvBhP/lu0PSD1TzYwkig== +"@tiptap/extension-code-block@^2.0.0-beta.13": + version "2.0.0-beta.13" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.13.tgz#9a41d103ed5b5a4810564c67105a89725b12559a" + integrity sha512-IjYxiEkHmcRQu9qSc/InalurSxQD33ti39VqwDRZqKKG0rkAZSJhKyETdopVjr3EuE6YIp38CJdQfma7OENUXw== dependencies: prosemirror-inputrules "^1.1.3" -"@tiptap/extension-code@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.6.tgz#c35f0a8b1f3fe05b5ec9ced5aea6c9b093073d09" - integrity sha512-i0s3yTSdONUOp0fM/VrdSQfdj0SsqTPyP2ev2Ji1KgzGQ49Rw8gewT6RorHMwvMdv+F5+wE47wI2rcdUjpNwMQ== +"@tiptap/extension-code@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.11.tgz#484d63cde6ad5951e9075e2a47ebe0f6b633742f" + integrity sha512-8u69zfc5Rd7QKZ3bZTj2IC4hJx34pT6lZ5u8XPS9DTlEPEXBp3XWgpZxuxFmoNhABqEPHYyBJao5eINtfQhhRg== -"@tiptap/extension-document@^2.0.0-beta.5": - version "2.0.0-beta.5" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.5.tgz#595c25879eb39f26cdb58e9b01ff5e48d65b2e4b" - integrity sha512-6GAZ7gA3vzStkASe+Qsd/OqqQ9QnDTjBKpXVxMZZnqutUmWjPau9e0kLEFYoU57f5bJa2w/TCWICSp+o4ka3jg== +"@tiptap/extension-document@^2.0.0-beta.10": + version "2.0.0-beta.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-2.0.0-beta.10.tgz#2ccf4e5496f6b15c6bfa4f720b89af74fb871df4" + integrity sha512-ugWixuQnZnmeXCk6Tp0lFWLdER5WL2QHJ9QOXSYoiT0LIfDivRhDvm4a0C0pToUL+f++jzfI8jcIvroHATWwVg== -"@tiptap/extension-dropcursor@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.6.tgz#9a7569c970010c47424e93f67f907ce7f0c3c429" - integrity sha512-EUmagYkamxuxZprKCXcSrwqUZkOW6edxIb7iyL0RQLYAcJ2jwCe9hJU0G6a8ILDr027W7fXd6LDbrzPMcVK/ug== +"@tiptap/extension-dropcursor@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.0-beta.11.tgz#620120a7f95e7cf21e6a3362ba38b2eacfa98a88" + integrity sha512-FwNoazT3BbvFsYxoQrmtVgLG1owEVhtoeaHndIWzMNxOGCWa96JtXsPgch9hSzQzb95qA5TFrFAftF/d5fkG8w== dependencies: "@types/prosemirror-dropcursor" "^1.0.1" prosemirror-dropcursor "^1.3.4" -"@tiptap/extension-floating-menu@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.6.tgz#617290bd0533412e40c09dde7ecadc4a8c3cf960" - integrity sha512-2j73cDaN+flG8mF/PHd8OrZjaG62r3Kbskzpdsa2Oa6fV3laNg0jMhFzcuBJwFKFa0l8RHB/zMXNpacxCHa9vw== +"@tiptap/extension-floating-menu@^2.0.0-beta.12": + version "2.0.0-beta.12" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.12.tgz#de38a4fc5e24c0f70caafeeece79e6623bd8a579" + integrity sha512-mPfBKCW9hatT2UueIpLZTnN4qJ8YmS3EktQBlZLOuYwrBPqDZXu2qCPn1CyIV3emOmfFvnOHO3n9sIqbDDbZaQ== dependencies: prosemirror-state "^1.3.4" - prosemirror-view "^1.18.2" + prosemirror-view "^1.18.4" tippy.js "^6.3.1" -"@tiptap/extension-gapcursor@^2.0.0-beta.10": - version "2.0.0-beta.10" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.10.tgz#f045d90def63559797576a299f904f539c304fe9" - integrity sha512-q5S3AjDTBi5WHwl1V7iYSk32t3mk/Z/ZYAViLDsqffzurx6KIxq9Yw6Ad1L+h04wXq/rJiFeaMeCnGs4DmWa3w== +"@tiptap/extension-gapcursor@^2.0.0-beta.15": + version "2.0.0-beta.15" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.15.tgz#6bafbd095e8673449f976935200f22a623d4043c" + integrity sha512-1qV9Wy4xa2oKD3JT5htK0Eu+tsPdknfXzWpdQT/P1NmTGk/Ysfy7CuipYNMaYD31NAreumXrC3CyDvel+1xo/A== dependencies: "@types/prosemirror-gapcursor" "^1.0.3" prosemirror-gapcursor "^1.1.5" -"@tiptap/extension-hard-break@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.6.tgz#37bee563b06a528d6b72ea875de7645f97a43656" - integrity sha512-FZ/wpC9YQY50rt85DuPl+Dxe157LtHAhKW08BRAds/o6zrwcBpbg7zzVPxnu1wH1L0ObhyxCjNFXUyKalLnk8g== +"@tiptap/extension-hard-break@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.11.tgz#b2d457bb5c4f97d052b2537a7624d9609822ef65" + integrity sha512-jjJDY6buBu/2hGp4lpO+jrfMPfqnTaRJxMVv21eZylFHsEvrBFfy5yAj5RdLaqHg8RO3Av2aRwztXFn6oiEQ1Q== -"@tiptap/extension-heading@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.6.tgz#077919803ed4d8c729a72cc122cfca851bfaeff3" - integrity sha512-zM5zaWGbJDYDmuHZ+YHTZK2nncDs+tlhfYKTKPK+0iIFCO4iTkvs7ERUO9qdbuQKjHGp28Q3RhS7YORss2bOhA== +"@tiptap/extension-heading@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.11.tgz#9da6864aeafaeed83551559932253c9ab16621fb" + integrity sha512-ymhb5q7iBIGFGU4hRrsVFJDx6zOUqF9qBBzufD44lT4KukFEqxjOO1RofOKI5V8VOTuhfx/hAt67fvwh4UR5iQ== dependencies: prosemirror-inputrules "^1.1.3" -"@tiptap/extension-history@^2.0.0-beta.5": - version "2.0.0-beta.5" - resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.5.tgz#bb9b257c110b0ad324407443972a623efa71816d" - integrity sha512-ej0QtStZVm8QInWHEtyduK9WQcYpfPN2EtIkwtPL9HFm9u7xgouBVdj1TqIABV3vJVGL28KKpGVVg8ZuBF4h7g== +"@tiptap/extension-history@^2.0.0-beta.10": + version "2.0.0-beta.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-history/-/extension-history-2.0.0-beta.10.tgz#bb14500e193118295eb8c55ae4c2ddc5f2ae5d72" + integrity sha512-iy6H4Y63hvIkD+W1YJ5fOpvhRXHurRuypZajnupommxRigorA3z8MTySsUs7PIKx3vYvxYjr4dKS/6Shs72DSQ== dependencies: "@types/prosemirror-history" "^1.0.2" prosemirror-history "^1.1.3" -"@tiptap/extension-horizontal-rule@^2.0.0-beta.7": - version "2.0.0-beta.7" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.7.tgz#6e75336a0316bef1a359de7d1888c8fb97d5e659" - integrity sha512-TOxoVyKL3qF0e+VCQ5B7BpdtspvvY0TdU6/AJVIatPK9rXXXcJTM68k0O4koXgeuu33CSPXWVNwgm3QcxMi3Dg== +"@tiptap/extension-horizontal-rule@^2.0.0-beta.14": + version "2.0.0-beta.14" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.14.tgz#bc23541ab5885c25a804b6b6f3c0ed2e505f7327" + integrity sha512-gCqJWhdqTfYKntlZ9dNlImFdFawRzfDbXDiffsnu1hqrEwCTweR5mWE+8qayRDL/JAEAqsFDLbgyPrYZQ1bvbA== dependencies: prosemirror-state "^1.3.4" -"@tiptap/extension-image@^2.0.0-beta.4": - version "2.0.0-beta.4" - resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.4.tgz#91151af0268b2de911ba9ea22099f00638f08d1e" - integrity sha512-NKqTmrxh8SmWUGex4QBq9Mjv7gIaJ8QlG//CXSW5s6QtLhjRbY9QFtBWK2FYOgyk2UBU6gmUA4wX6Eb0KGa4XQ== +"@tiptap/extension-image@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.11.tgz#b7c2961118c9468fd4bdb8632663379f618978e9" + integrity sha512-0ItQgR6laHLqamWH4aEqbxPiiU2Vd9rTXnSKH/UE6N+O/7eyhSb/ABbbxVdS9P7EmnuDhlo2F12ysNgI1D9Uyw== -"@tiptap/extension-italic@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.6.tgz#497cc5aac4077b27d6c548896601dd240771401a" - integrity sha512-HjB6YCm4oQ04peQ9M2zi4101JSgNfOLTkyfbDhpQv+B61sZtuweJx27SxYDOB34dA+i513orCVZdI6AgSSCEHA== +"@tiptap/extension-italic@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.11.tgz#fa5668ede5dd5373277da4bafc26415a2771d7f6" + integrity sha512-bm7iYB6HZTWa/ncU83hcAvjXjB7hei86xG5u+Hl2d11PvR6j/6srn1FWcXmf/2xwuWI/mUCTpl+ZfkoY7oTu9w== -"@tiptap/extension-link@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.6.tgz#8e0051593d24ee232387535de716f87d4c388062" - integrity sha512-zV7AJTgvGfYqtKYeywLg0lDLkYFynRW6Llh6C+hYw2w9Wq0fSZfTtpkcQPYv+jOcOoAFi2Ea02jyGQ+VthGZKg== +"@tiptap/extension-link@^2.0.0-beta.15": + version "2.0.0-beta.15" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.15.tgz#bb9adad12c55688e220f7d8a15d8456fbad47116" + integrity sha512-/iIc1PjLSrteezXn8wmdKrXsMHS8zwV+UiPOSFPaGYzmJTt3kA+FVOo7xLOV1dZjM+nWDd02/QFNBNUCjGOeug== dependencies: prosemirror-state "^1.3.4" -"@tiptap/extension-list-item@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.6.tgz#69850d2d9242e213a24a7e10baecb0f0933f3cbe" - integrity sha512-zhssny5W2Q7CvB9qZT1Wc7k0V+R7IqCbNBmoijwF9a+uehBpJcxdN1DFB1v0qdmIEdDLU9dnBUfIpWPnLwiAXw== +"@tiptap/extension-list-item@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.11.tgz#22639904f9fdee8b80e344fe142e9065e9451337" + integrity sha512-HKA9q2q9AobbB3er12FbXAy3rHVd41LXFKegpP/2Fle3gembN+VlazSXqHHWzlbaHgQAVn1MP6pnwaDFDYGNeA== -"@tiptap/extension-ordered-list@^2.0.0-beta.6": - version "2.0.0-beta.6" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.6.tgz#ce214854946284c7ff2d458c7f4b3ac6ae33a9b8" - integrity sha512-dUiTO9bV3cuxWedp164KVufW3BzIwY/beQ64aQjnRyA3TPyiPrhp4qvHrxQujm31XPJy4zUY0PO/VafJ+69cGw== +"@tiptap/extension-ordered-list@^2.0.0-beta.11": + version "2.0.0-beta.11" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.11.tgz#37dd6cbb8b271bf8902ffcbad2a2e282ea078f6e" + integrity sha512-EYv8jKahmGgumIz/MHfI1zsGTz7YgTII7WdKPG8C3hpkDdkg/tb28Ue14CXlQ2hSGWjFTf1U0gHgHcPaJHMYJg== dependencies: prosemirror-inputrules "^1.1.3" -"@tiptap/extension-paragraph@^2.0.0-beta.7": - version "2.0.0-beta.7" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.7.tgz#5dca1524247a57e98a60eb394ae66cd30072b9f4" - integrity sha512-bk9/KNbJ4wAvaAwrPLwp89QtEyef9VxbbaPd2+Q21EArTP2SGPNTWrjARq1Flc41fERo+2A23K5AcbNDBID9DA== +"@tiptap/extension-paragraph@^2.0.0-beta.12": + version "2.0.0-beta.12" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.12.tgz#849aad4294a341abd93781174dbdc369d9b7570c" + integrity sha512-qzURExCJ9gKLnzRE219BWp0S/wNNhX8NQI+ipMSIRoh5wZInCHwZnObRLsGuQI/qH/lQ0Y0QYWzA8VknCaH1kA== -"@tiptap/extension-strike@^2.0.0-beta.7": - version "2.0.0-beta.7" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.7.tgz#cee0f3c087d2914f8435dc2cc4580c9aec0a42bb" - integrity sha512-GBctBeHSkDW4ivXAUaEBtOgQXJgT2q2iqWuI8kTHCO1z7c/mns4J19U24dx8bPFhJBw++sDDd8yBkLQH2lP/Pw== +"@tiptap/extension-strike@^2.0.0-beta.12": + version "2.0.0-beta.12" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.12.tgz#0a7452e92d5042c8884eda38f6496a1ecc28e264" + integrity sha512-qAJiC31BmRUOnDzGmMev2PMR5hhTIGIlH3aeZDiIsfiGCUleuyM3qJHIasq11W4Yap0s52qsSGI0+cmdVkbR/w== -"@tiptap/extension-text@^2.0.0-beta.5": - version "2.0.0-beta.5" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.5.tgz#cce1f9b22a5e0ad35b081774059b8c2ec6c92ae3" - integrity sha512-WCavPVqi+tndW8tAQ4KBq98ZnkLgKW9nc/T8wE3oKQ+df9YBauIl3OxxMA6At/oK+vlcFfubBpzFRAg9iygRAA== +"@tiptap/extension-text@^2.0.0-beta.10": + version "2.0.0-beta.10" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.10.tgz#50681ba2c8e8a54305a89efb1adb0e68d683e4eb" + integrity sha512-4AdB5e88FmDbBWe9nTOCpvV5QpY2RoBKMqdSTsJcBhxGCXspomgOjbjYMtMabNkhd5mt76l2l65TyjRKSh5BCQ== -"@tiptap/vue-2@^2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.21.tgz#838257ef91bbd54995aa7d34e4682fc9c69cec6a" - integrity sha512-WTL6iw6cgMkQQ2b++kClQOxsByAUKYLcjO1UsjmrrWnaSDmfMO1ZpkmKKSp1SsuQAk7W0t9aybeyWrDzjxfU3g== +"@tiptap/vue-2@^2.0.0-beta.27": + version "2.0.0-beta.27" + resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.27.tgz#f9e3242a75957d46f1a4707e4a1bb29ec1d19e46" + integrity sha512-8SG5EutZL+//Vu967yRZkSFatDXViOhRmlMxJpuK4xPL3/Y9EeqbmrK6NjZh7/nScTtTlg35oAwFZ2Sss818gw== dependencies: - "@tiptap/extension-bubble-menu" "^2.0.0-beta.9" - "@tiptap/extension-floating-menu" "^2.0.0-beta.6" - prosemirror-view "^1.18.2" + "@tiptap/extension-bubble-menu" "^2.0.0-beta.15" + "@tiptap/extension-floating-menu" "^2.0.0-beta.12" + prosemirror-view "^1.18.4" "@toast-ui/editor@^2.5.2": version "2.5.2" @@ -9597,10 +9597,10 @@ prosemirror-markdown@^1.5.1: markdown-it "^10.0.0" prosemirror-model "^1.0.0" -prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.13.1, prosemirror-model@^1.13.3, prosemirror-model@^1.14.0, prosemirror-model@^1.8.1: - version "1.14.0" - resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.14.0.tgz#44042a16942dfc5dcd79daf6ec37b0efcfef53c8" - integrity sha512-+9J7YE2qD2lsRgaI5aF7u6LynBoHxb/8sW1gaMKRAhK+yeQ+motBIaxb2GxRWSadDWMOq5haAImSTBo6jDkv2A== +prosemirror-model@^1.0.0, prosemirror-model@^1.1.0, prosemirror-model@^1.13.1, prosemirror-model@^1.13.3, prosemirror-model@^1.14.1, prosemirror-model@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.14.1.tgz#d784c67f95a5d66b853e82ff9a87a50353ef9cd5" + integrity sha512-vZcbI+24VloFefKZkDnMaEpipL/vSKKPdFiik4KOnTzq3e6AO7+CAOixZ2G/SsfRaYC965XvnOIEbhIQdgki7w== dependencies: orderedmap "^1.1.0" @@ -9638,10 +9638,10 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor dependencies: prosemirror-model "^1.0.0" -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.18.2: - version "1.18.3" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.18.3.tgz#cfc70169cb300e9503d97362463ea870efffd3ef" - integrity sha512-B0zlzjBI0cHadpghyvAA+JgqLGbkNU9Vxywqkfaa+AdmOZUZImBKH6ufhpK+AEZn97WWgSIkr/MT9RmGpaboAA== +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.18.4: + version "1.18.4" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.18.4.tgz#179141df117cf414434ade08115f2e233d135f6d" + integrity sha512-6oi62XRK5WxhMX1Amjk5uMsWILUEcFbFF75i09BzpAdI+5glhs7heCaRvKOj4v3YRJ7LJVkOXS9xvjetlE3+pA== dependencies: prosemirror-model "^1.1.0" prosemirror-state "^1.0.0" |