Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-11 18:10:36 +00:00
parent e3042fc5ce
commit f020d5dc9b
75 changed files with 1339 additions and 546 deletions

View File

@ -1 +1 @@
30ae36f781ee979330b1f170d81c97c319c2fff1
4a1b0d4018ee35cfe786ba3dd975b20013a39e39

View File

@ -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 cant 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 cant 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 cant commit changes through the Web IDE.',
);
export const MSG_CANNOT_PUSH_UNSIGNED_SHORT = s__(

View File

@ -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);
},

View File

@ -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]: {

View File

@ -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),

View File

@ -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';

View File

@ -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) => ({

View File

@ -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>

View File

@ -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 = {

View File

@ -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;

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,10 +1,7 @@
- message_base = s_("ForkSuggestion|You cant %{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')

View File

@ -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

View File

@ -6,7 +6,7 @@
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
.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 cant %{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

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Add more attributes to the blob GraphQL API
merge_request: 61155
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add Gradle Kotlin installations commands
merge_request: 60097
author: Cromefire_ (@cromefire_)
type: changed

View File

@ -0,0 +1,5 @@
---
title: Update messages when user cannot directly push code to project
merge_request: 61071
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: 'GithubImport: Fix Review importer when the author does not exist anymore'
merge_request: 61257
author:
type: fixed

2
config/storage.yml Normal file
View File

@ -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

View File

@ -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.
data as payload. The response code from the external service determines if GitLab
should accept or reject the pipeline. If the response is:
Response codes:
- `200`, the pipeline is accepted.
- `4XX`, the pipeline is rejected.
- Other codes, the pipeline is accepted and logged.
- `200`: Accepted
- `4XX`: Rejected
- All other codes: accepted and logged
If there's an error or the request times out, the pipeline is accepted.
### Service Result
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.
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`
## 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
{

View File

@ -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. |

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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),

View File

@ -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 cant %{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,18 +36159,18 @@ 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 cant commit changes through the Web IDE."
msgstr ""
msgid "WebIDE|You cant edit files directly in this project. Fork this project and submit a merge request with your changes."
msgstr ""
msgid "WebIDE|You cant 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."
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."
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."
msgstr ""
msgid "WebexTeamsService|Send notifications about project events to Webex Teams."
msgstr ""
@ -36959,6 +36983,9 @@ msgstr ""
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
msgid "You cant %{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 ""

View File

@ -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",

View File

@ -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 cant edit files directly in this project. "\
"Fork this project and submit a merge request with your changes."
)
end

View File

@ -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) }
before do
sign_in(user)
end
it 'verifies confirmation of additional email' do
sign_in(user)
email = create(:email, user: user)
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)
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

View File

@ -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'],
};

View File

@ -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 };
});

View File

@ -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',
});

View File

@ -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',
});
});
});

View File

@ -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"
/>

View File

@ -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,
});
});
});
});
});

View File

@ -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' });

View File

@ -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 = {

View File

@ -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', () => {

View File

@ -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

View File

@ -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) }
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) }
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) }
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) }
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)),
it 'returns expected json' do
expected = {
member_path: '/groups/foo-bar/-/group_members/:id',
source_id: group.id,
can_manage_members: 'true'
})
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_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_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_members) { create_list(:group_member, 2, :access_request, group: group, created_by: current_user) }
it_behaves_like 'members.json'
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
expect(queries.count).to eq(1)
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) }
subject { described_class.created_before(2.hours.ago) }
it do
expect(subject.to_a).to contain_exactly(component_file1, component_file2)
end
end
end

View File

@ -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) }

View File

@ -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

236
yarn.lock
View File

@ -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"