Welcome to mirror list, hosted at ThFree Co, Russian Federation.

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