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:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-09-27 15:13:56 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-09-27 15:13:56 +0300
commitcddf2db96b2280ad995b589b70ff23ff77cceb7b (patch)
treea880d70b014d5bc776e439013605bbb77bb76336
parent1b044a566c8c1b7d835dacbd2d7471d7fed32082 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/ide/components/preview/navigator.vue2
-rw-r--r--app/assets/javascripts/packages/details/components/additional_metadata.vue94
-rw-r--r--app/assets/javascripts/packages/details/components/app.vue68
-rw-r--r--app/assets/javascripts/packages/details/components/composer_installation.vue65
-rw-r--r--app/assets/javascripts/packages/details/components/conan_installation.vue59
-rw-r--r--app/assets/javascripts/packages/details/components/dependency_row.vue35
-rw-r--r--app/assets/javascripts/packages/details/components/installation_commands.vue55
-rw-r--r--app/assets/javascripts/packages/details/components/installation_title.vue38
-rw-r--r--app/assets/javascripts/packages/details/components/maven_installation.vue153
-rw-r--r--app/assets/javascripts/packages/details/components/npm_installation.vue103
-rw-r--r--app/assets/javascripts/packages/details/components/nuget_installation.vue58
-rw-r--r--app/assets/javascripts/packages/details/components/package_title.vue113
-rw-r--r--app/assets/javascripts/packages/details/components/pypi_installation.vue71
-rw-r--r--app/graphql/types/packages/package_type.rb2
-rw-r--r--app/models/concerns/issue_available_features.rb3
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/services/issues/clone_service.rb22
-rw-r--r--app/services/issues/move_service.rb22
-rw-r--r--config/feature_flags/development/paginated_tree_graphql_query.yml2
-rw-r--r--doc/api/environments.md14
-rw-r--r--doc/api/graphql/reference/index.md3
-rw-r--r--doc/development/import_project.md6
-rw-r--r--doc/raketasks/index.md11
-rw-r--r--doc/user/application_security/dependency_scanning/index.md1
-rw-r--r--doc/user/clusters/management_project_template.md2
-rw-r--r--lib/api/entities/environment_basic.rb2
-rw-r--r--lib/gitlab/regex.rb6
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/features/projects/infrastructure_registry_spec.rb6
-rw-r--r--spec/fixtures/api/schemas/graphql/packages/package_details.json4
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/environment.json7
-rw-r--r--spec/frontend/packages/details/components/__snapshots__/conan_installation_spec.js.snap36
-rw-r--r--spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap34
-rw-r--r--spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap112
-rw-r--r--spec/frontend/packages/details/components/__snapshots__/npm_installation_spec.js.snap36
-rw-r--r--spec/frontend/packages/details/components/__snapshots__/nuget_installation_spec.js.snap36
-rw-r--r--spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap168
-rw-r--r--spec/frontend/packages/details/components/__snapshots__/pypi_installation_spec.js.snap45
-rw-r--r--spec/frontend/packages/details/components/additional_metadata_spec.js119
-rw-r--r--spec/frontend/packages/details/components/app_spec.js96
-rw-r--r--spec/frontend/packages/details/components/composer_installation_spec.js133
-rw-r--r--spec/frontend/packages/details/components/conan_installation_spec.js72
-rw-r--r--spec/frontend/packages/details/components/dependency_row_spec.js62
-rw-r--r--spec/frontend/packages/details/components/installation_title_spec.js58
-rw-r--r--spec/frontend/packages/details/components/installations_commands_spec.js61
-rw-r--r--spec/frontend/packages/details/components/maven_installation_spec.js184
-rw-r--r--spec/frontend/packages/details/components/npm_installation_spec.js123
-rw-r--r--spec/frontend/packages/details/components/nuget_installation_spec.js79
-rw-r--r--spec/frontend/packages/details/components/package_title_spec.js189
-rw-r--r--spec/frontend/packages/details/components/pypi_installation_spec.js72
-rw-r--r--spec/lib/gitlab/regex_spec.rb15
-rw-r--r--spec/models/issue_spec.rb20
-rw-r--r--spec/requests/api/environments_spec.rb7
-rw-r--r--spec/services/projects/update_service_spec.rb2
-rw-r--r--spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb6
55 files changed, 159 insertions, 2643 deletions
diff --git a/app/assets/javascripts/ide/components/preview/navigator.vue b/app/assets/javascripts/ide/components/preview/navigator.vue
index 838c363a6a3..96f9a85c23f 100644
--- a/app/assets/javascripts/ide/components/preview/navigator.vue
+++ b/app/assets/javascripts/ide/components/preview/navigator.vue
@@ -117,7 +117,7 @@ export default {
class="ide-navigator-btn d-flex align-items-center d-transparent border-0 bg-transparent"
@click="refresh"
>
- <gl-icon :size="18" name="retry" use-deprecated-sizes class="m-auto" />
+ <gl-icon :size="16" name="retry" class="m-auto" />
</button>
<div class="position-relative w-100 gl-ml-2">
<input
diff --git a/app/assets/javascripts/packages/details/components/additional_metadata.vue b/app/assets/javascripts/packages/details/components/additional_metadata.vue
deleted file mode 100644
index 4e99099b0a1..00000000000
--- a/app/assets/javascripts/packages/details/components/additional_metadata.vue
+++ /dev/null
@@ -1,94 +0,0 @@
-<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
-import { PackageType } from '../../shared/constants';
-
-export default {
- i18n: {
- sourceText: s__('PackageRegistry|Source project located at %{link}'),
- licenseText: s__('PackageRegistry|License information located at %{link}'),
- recipeText: s__('PackageRegistry|Recipe: %{recipe}'),
- appGroup: s__('PackageRegistry|App group: %{group}'),
- appName: s__('PackageRegistry|App name: %{name}'),
- },
- components: {
- DetailsRow,
- GlLink,
- GlSprintf,
- },
- props: {
- packageEntity: {
- type: Object,
- required: true,
- },
- },
- computed: {
- showMetadata() {
- const visibilityConditions = {
- [PackageType.NUGET]: this.packageEntity.nuget_metadatum,
- [PackageType.CONAN]: this.packageEntity.conan_metadatum,
- [PackageType.MAVEN]: this.packageEntity.maven_metadatum,
- };
- return visibilityConditions[this.packageEntity.package_type];
- },
- },
-};
-</script>
-
-<template>
- <div v-if="showMetadata">
- <h3 class="gl-font-lg" data-testid="title">{{ __('Additional Metadata') }}</h3>
-
- <div class="gl-bg-gray-50 gl-inset-border-1-gray-100 gl-rounded-base" data-testid="main">
- <template v-if="packageEntity.nuget_metadatum">
- <details-row icon="project" padding="gl-p-4" dashed data-testid="nuget-source">
- <gl-sprintf :message="$options.i18n.sourceText">
- <template #link>
- <gl-link :href="packageEntity.nuget_metadatum.project_url" target="_blank">{{
- packageEntity.nuget_metadatum.project_url
- }}</gl-link>
- </template>
- </gl-sprintf>
- </details-row>
- <details-row icon="license" padding="gl-p-4" data-testid="nuget-license">
- <gl-sprintf :message="$options.i18n.licenseText">
- <template #link>
- <gl-link :href="packageEntity.nuget_metadatum.license_url" target="_blank">{{
- packageEntity.nuget_metadatum.license_url
- }}</gl-link>
- </template>
- </gl-sprintf>
- </details-row>
- </template>
-
- <details-row
- v-else-if="packageEntity.conan_metadatum"
- icon="information-o"
- padding="gl-p-4"
- data-testid="conan-recipe"
- >
- <gl-sprintf :message="$options.i18n.recipeText">
- <template #recipe>{{ packageEntity.name }}</template>
- </gl-sprintf>
- </details-row>
-
- <template v-else-if="packageEntity.maven_metadatum">
- <details-row icon="information-o" padding="gl-p-4" dashed data-testid="maven-app">
- <gl-sprintf :message="$options.i18n.appName">
- <template #name>
- <strong>{{ packageEntity.maven_metadatum.app_name }}</strong>
- </template>
- </gl-sprintf>
- </details-row>
- <details-row icon="information-o" padding="gl-p-4" data-testid="maven-group">
- <gl-sprintf :message="$options.i18n.appGroup">
- <template #group>
- <strong>{{ packageEntity.maven_metadatum.app_group }}</strong>
- </template>
- </gl-sprintf>
- </details-row>
- </template>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/packages/details/components/app.vue b/app/assets/javascripts/packages/details/components/app.vue
index 59da32e6666..462252ce944 100644
--- a/app/assets/javascripts/packages/details/components/app.vue
+++ b/app/assets/javascripts/packages/details/components/app.vue
@@ -1,6 +1,5 @@
<script>
import {
- GlBadge,
GlButton,
GlModal,
GlModalDirective,
@@ -14,36 +13,30 @@ import { mapActions, mapState } from 'vuex';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { objectToQuery } from '~/lib/utils/url_utility';
import { s__, __ } from '~/locale';
+import TerraformTitle from '~/packages_and_registries/infrastructure_registry/components/details_title.vue';
+import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
import Tracking from '~/tracking';
import PackageListRow from '../../shared/components/package_list_row.vue';
import PackagesListLoader from '../../shared/components/packages_list_loader.vue';
-import { PackageType, TrackingActions, SHOW_DELETE_SUCCESS_ALERT } from '../../shared/constants';
+import { TrackingActions, SHOW_DELETE_SUCCESS_ALERT } from '../../shared/constants';
import { packageTypeToTrackCategory } from '../../shared/utils';
-import AdditionalMetadata from './additional_metadata.vue';
-import DependencyRow from './dependency_row.vue';
-import InstallationCommands from './installation_commands.vue';
import PackageFiles from './package_files.vue';
import PackageHistory from './package_history.vue';
export default {
name: 'PackagesApp',
components: {
- GlBadge,
GlButton,
GlEmptyState,
GlModal,
GlTab,
GlTabs,
GlSprintf,
- PackageTitle: () => import('./package_title.vue'),
- TerraformTitle: () =>
- import('~/packages_and_registries/infrastructure_registry/components/details_title.vue'),
+ TerraformTitle,
PackagesListLoader,
PackageListRow,
- DependencyRow,
PackageHistory,
- AdditionalMetadata,
- InstallationCommands,
+ TerraformInstallation,
PackageFiles,
},
directives: {
@@ -51,12 +44,6 @@ export default {
GlModal: GlModalDirective,
},
mixins: [Tracking.mixin()],
- inject: {
- titleComponent: {
- default: 'PackageTitle',
- from: 'titleComponent',
- },
- },
trackingActions: { ...TrackingActions },
data() {
return {
@@ -87,15 +74,6 @@ export default {
hasVersions() {
return this.packageEntity.versions?.length > 0;
},
- packageDependencies() {
- return this.packageEntity.dependency_links || [];
- },
- showDependencies() {
- return this.packageEntity.package_type === PackageType.NUGET;
- },
- showFiles() {
- return this.packageEntity?.package_type !== PackageType.COMPOSER;
- },
},
methods: {
...mapActions(['deletePackage', 'fetchPackageVersions', 'deletePackageFile']),
@@ -167,7 +145,7 @@ export default {
/>
<div v-else class="packages-app">
- <component :is="titleComponent">
+ <terraform-title>
<template #delete-button>
<gl-button
v-if="canDelete"
@@ -180,24 +158,16 @@ export default {
{{ __('Delete') }}
</gl-button>
</template>
- </component>
+ </terraform-title>
<gl-tabs>
<gl-tab :title="__('Detail')">
<div data-qa-selector="package_information_content">
<package-history :package-entity="packageEntity" :project-name="projectName" />
-
- <installation-commands
- :package-entity="packageEntity"
- :npm-path="npmPath"
- :npm-help-path="npmHelpPath"
- />
-
- <additional-metadata :package-entity="packageEntity" />
+ <terraform-installation />
</div>
<package-files
- v-if="showFiles"
:package-files="packageFiles"
:can-delete="canDelete"
@download-file="track($options.trackingActions.PULL_PACKAGE)"
@@ -205,27 +175,6 @@ export default {
/>
</gl-tab>
- <gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab">
- <template #title>
- <span>{{ __('Dependencies') }}</span>
- <gl-badge size="sm" data-testid="dependencies-badge">{{
- packageDependencies.length
- }}</gl-badge>
- </template>
-
- <template v-if="packageDependencies.length > 0">
- <dependency-row
- v-for="(dep, index) in packageDependencies"
- :key="index"
- :dependency="dep"
- />
- </template>
-
- <p v-else class="gl-mt-3" data-testid="no-dependencies-message">
- {{ s__('PackageRegistry|This NuGet package has no dependencies.') }}
- </p>
- </gl-tab>
-
<gl-tab
:title="__('Other versions')"
title-item-class="js-versions-tab"
@@ -254,7 +203,6 @@ export default {
<gl-modal
ref="deleteModal"
- class="js-delete-modal"
modal-id="delete-modal"
:action-primary="$options.modal.packageDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
diff --git a/app/assets/javascripts/packages/details/components/composer_installation.vue b/app/assets/javascripts/packages/details/components/composer_installation.vue
deleted file mode 100644
index bf1e5083e12..00000000000
--- a/app/assets/javascripts/packages/details/components/composer_installation.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
-import { mapGetters, mapState } from 'vuex';
-import { s__ } from '~/locale';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
-import { TrackingActions, TrackingLabels } from '../constants';
-
-export default {
- name: 'ComposerInstallation',
- components: {
- InstallationTitle,
- CodeInstruction,
- GlLink,
- GlSprintf,
- },
- computed: {
- ...mapState(['composerHelpPath']),
- ...mapGetters(['composerRegistryInclude', 'composerPackageInclude', 'groupExists']),
- },
- i18n: {
- registryInclude: s__('PackageRegistry|Add composer registry'),
- copyRegistryInclude: s__('PackageRegistry|Copy registry include'),
- packageInclude: s__('PackageRegistry|Install package version'),
- copyPackageInclude: s__('PackageRegistry|Copy require package include'),
- infoLine: s__(
- 'PackageRegistry|For more information on Composer packages in GitLab, %{linkStart}see the documentation.%{linkEnd}',
- ),
- },
- trackingActions: { ...TrackingActions },
- TrackingLabels,
- installOptions: [{ value: 'composer', label: s__('PackageRegistry|Show Composer commands') }],
-};
-</script>
-
-<template>
- <div v-if="groupExists" data-testid="root-node">
- <installation-title package-type="composer" :options="$options.installOptions" />
-
- <code-instruction
- :label="$options.i18n.registryInclude"
- :instruction="composerRegistryInclude"
- :copy-text="$options.i18n.copyRegistryInclude"
- :tracking-action="$options.trackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- data-testid="registry-include"
- />
-
- <code-instruction
- :label="$options.i18n.packageInclude"
- :instruction="composerPackageInclude"
- :copy-text="$options.i18n.copyPackageInclude"
- :tracking-action="$options.trackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- data-testid="package-include"
- />
- <span data-testid="help-text">
- <gl-sprintf :message="$options.i18n.infoLine">
- <template #link="{ content }">
- <gl-link :href="composerHelpPath" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </span>
- </div>
-</template>
diff --git a/app/assets/javascripts/packages/details/components/conan_installation.vue b/app/assets/javascripts/packages/details/components/conan_installation.vue
deleted file mode 100644
index 1d855f6cf3e..00000000000
--- a/app/assets/javascripts/packages/details/components/conan_installation.vue
+++ /dev/null
@@ -1,59 +0,0 @@
-<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
-import { mapGetters, mapState } from 'vuex';
-import { s__ } from '~/locale';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
-import { TrackingActions, TrackingLabels } from '../constants';
-
-export default {
- name: 'ConanInstallation',
- components: {
- InstallationTitle,
- CodeInstruction,
- GlLink,
- GlSprintf,
- },
- computed: {
- ...mapState(['conanHelpPath']),
- ...mapGetters(['conanInstallationCommand', 'conanSetupCommand']),
- },
- i18n: {
- helpText: s__(
- 'PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}.',
- ),
- },
- trackingActions: { ...TrackingActions },
- TrackingLabels,
- installOptions: [{ value: 'conan', label: s__('PackageRegistry|Show Conan commands') }],
-};
-</script>
-
-<template>
- <div>
- <installation-title package-type="conan" :options="$options.installOptions" />
-
- <code-instruction
- :label="s__('PackageRegistry|Conan Command')"
- :instruction="conanInstallationCommand"
- :copy-text="s__('PackageRegistry|Copy Conan Command')"
- :tracking-action="$options.trackingActions.COPY_CONAN_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
-
- <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
-
- <code-instruction
- :label="s__('PackageRegistry|Add Conan Remote')"
- :instruction="conanSetupCommand"
- :copy-text="s__('PackageRegistry|Copy Conan Setup Command')"
- :tracking-action="$options.trackingActions.COPY_CONAN_SETUP_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
- <gl-sprintf :message="$options.i18n.helpText">
- <template #link="{ content }">
- <gl-link :href="conanHelpPath" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </div>
-</template>
diff --git a/app/assets/javascripts/packages/details/components/dependency_row.vue b/app/assets/javascripts/packages/details/components/dependency_row.vue
deleted file mode 100644
index 1a2202b23c8..00000000000
--- a/app/assets/javascripts/packages/details/components/dependency_row.vue
+++ /dev/null
@@ -1,35 +0,0 @@
-<script>
-export default {
- name: 'DependencyRow',
- props: {
- dependency: {
- type: Object,
- required: true,
- },
- },
- computed: {
- showVersion() {
- return Boolean(this.dependency.version_pattern);
- },
- },
-};
-</script>
-
-<template>
- <div class="gl-responsive-table-row">
- <div class="table-section section-50">
- <strong class="gl-text-body">{{ dependency.name }}</strong>
- <span v-if="dependency.target_framework" data-testid="target-framework"
- >({{ dependency.target_framework }})</span
- >
- </div>
-
- <div
- v-if="showVersion"
- class="table-section section-50 gl-display-flex gl-md-justify-content-end"
- data-testid="version-pattern"
- >
- <span class="gl-text-body">{{ dependency.version_pattern }}</span>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/packages/details/components/installation_commands.vue b/app/assets/javascripts/packages/details/components/installation_commands.vue
deleted file mode 100644
index ed55d7fe782..00000000000
--- a/app/assets/javascripts/packages/details/components/installation_commands.vue
+++ /dev/null
@@ -1,55 +0,0 @@
-<script>
-import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
-import { PackageType, TERRAFORM_PACKAGE_TYPE } from '../../shared/constants';
-import ComposerInstallation from './composer_installation.vue';
-import ConanInstallation from './conan_installation.vue';
-import MavenInstallation from './maven_installation.vue';
-import NpmInstallation from './npm_installation.vue';
-import NugetInstallation from './nuget_installation.vue';
-import PypiInstallation from './pypi_installation.vue';
-
-export default {
- name: 'InstallationCommands',
- components: {
- [PackageType.CONAN]: ConanInstallation,
- [PackageType.MAVEN]: MavenInstallation,
- [PackageType.NPM]: NpmInstallation,
- [PackageType.NUGET]: NugetInstallation,
- [PackageType.PYPI]: PypiInstallation,
- [PackageType.COMPOSER]: ComposerInstallation,
- [TERRAFORM_PACKAGE_TYPE]: TerraformInstallation,
- },
- props: {
- packageEntity: {
- type: Object,
- required: true,
- },
- npmPath: {
- type: String,
- required: false,
- default: '',
- },
- npmHelpPath: {
- type: String,
- required: false,
- default: '',
- },
- },
- computed: {
- installationComponent() {
- return this.$options.components[this.packageEntity.package_type];
- },
- },
-};
-</script>
-
-<template>
- <div v-if="installationComponent">
- <component
- :is="installationComponent"
- :name="packageEntity.name"
- :registry-url="npmPath"
- :help-url="npmHelpPath"
- />
- </div>
-</template>
diff --git a/app/assets/javascripts/packages/details/components/installation_title.vue b/app/assets/javascripts/packages/details/components/installation_title.vue
deleted file mode 100644
index 43133bf7825..00000000000
--- a/app/assets/javascripts/packages/details/components/installation_title.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-<script>
-import PersistedDropdownSelection from '~/vue_shared/components/registry/persisted_dropdown_selection.vue';
-
-export default {
- name: 'InstallationTitle',
- components: {
- PersistedDropdownSelection,
- },
- props: {
- packageType: {
- type: String,
- required: true,
- },
- options: {
- type: Array,
- required: true,
- },
- },
- computed: {
- storageKey() {
- return `package_${this.packageType}_installation_instructions`;
- },
- },
-};
-</script>
-
-<template>
- <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center">
- <h3 class="gl-font-lg">{{ __('Installation') }}</h3>
- <div>
- <persisted-dropdown-selection
- :storage-key="storageKey"
- :options="options"
- @change="$emit('change', $event)"
- />
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/packages/details/components/maven_installation.vue b/app/assets/javascripts/packages/details/components/maven_installation.vue
deleted file mode 100644
index 6974de99344..00000000000
--- a/app/assets/javascripts/packages/details/components/maven_installation.vue
+++ /dev/null
@@ -1,153 +0,0 @@
-<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
-import { mapGetters, mapState } from 'vuex';
-import { s__ } from '~/locale';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
-
-import { TrackingActions, TrackingLabels } from '../constants';
-
-export default {
- name: 'MavenInstallation',
- components: {
- InstallationTitle,
- CodeInstruction,
- GlLink,
- GlSprintf,
- },
- data() {
- return {
- instructionType: 'maven',
- };
- },
- computed: {
- ...mapState(['mavenHelpPath']),
- ...mapGetters([
- 'mavenInstallationXml',
- 'mavenInstallationCommand',
- 'mavenSetupXml',
- 'gradleGroovyInstalCommand',
- 'gradleGroovyAddSourceCommand',
- 'gradleKotlinInstalCommand',
- 'gradleKotlinAddSourceCommand',
- ]),
- showMaven() {
- return this.instructionType === 'maven';
- },
- showGroovy() {
- return this.instructionType === 'groovy';
- },
- },
- i18n: {
- xmlText: s__(
- `PackageRegistry|Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block.`,
- ),
- setupText: s__(
- `PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file.`,
- ),
- helpText: s__(
- 'PackageRegistry|For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}.',
- ),
- },
- trackingActions: { ...TrackingActions },
- TrackingLabels,
- installOptions: [
- { value: 'maven', label: s__('PackageRegistry|Maven XML') },
- { value: 'groovy', label: s__('PackageRegistry|Gradle Groovy DSL') },
- { value: 'kotlin', label: s__('PackageRegistry|Gradle Kotlin DSL') },
- ],
-};
-</script>
-
-<template>
- <div>
- <installation-title
- package-type="maven"
- :options="$options.installOptions"
- @change="instructionType = $event"
- />
-
- <template v-if="showMaven">
- <p>
- <gl-sprintf :message="$options.i18n.xmlText">
- <template #code="{ content }">
- <code>{{ content }}</code>
- </template>
- </gl-sprintf>
- </p>
-
- <code-instruction
- :instruction="mavenInstallationXml"
- :copy-text="s__('PackageRegistry|Copy Maven XML')"
- :tracking-action="$options.trackingActions.COPY_MAVEN_XML"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- multiline
- />
-
- <code-instruction
- :label="s__('PackageRegistry|Maven Command')"
- :instruction="mavenInstallationCommand"
- :copy-text="s__('PackageRegistry|Copy Maven command')"
- :tracking-action="$options.trackingActions.COPY_MAVEN_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
-
- <h3 class="gl-font-lg">{{ s__('PackageRegistry|Registry setup') }}</h3>
- <p>
- <gl-sprintf :message="$options.i18n.setupText">
- <template #code="{ content }">
- <code>{{ content }}</code>
- </template>
- </gl-sprintf>
- </p>
- <code-instruction
- :instruction="mavenSetupXml"
- :copy-text="s__('PackageRegistry|Copy Maven registry XML')"
- :tracking-action="$options.trackingActions.COPY_MAVEN_SETUP"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- multiline
- />
- <gl-sprintf :message="$options.i18n.helpText">
- <template #link="{ content }">
- <gl-link :href="mavenHelpPath" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </template>
- <template v-else-if="showGroovy">
- <code-instruction
- class="gl-mb-5"
- :label="s__('PackageRegistry|Gradle Groovy DSL install command')"
- :instruction="gradleGroovyInstalCommand"
- :copy-text="s__('PackageRegistry|Copy Gradle Groovy DSL install command')"
- :tracking-action="$options.trackingActions.COPY_GRADLE_INSTALL_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
- <code-instruction
- :label="s__('PackageRegistry|Add Gradle Groovy DSL repository command')"
- :instruction="gradleGroovyAddSourceCommand"
- :copy-text="s__('PackageRegistry|Copy add Gradle Groovy DSL repository command')"
- :tracking-action="$options.trackingActions.COPY_GRADLE_ADD_TO_SOURCE_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- 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/components/npm_installation.vue b/app/assets/javascripts/packages/details/components/npm_installation.vue
deleted file mode 100644
index 6b0fcf5e4fe..00000000000
--- a/app/assets/javascripts/packages/details/components/npm_installation.vue
+++ /dev/null
@@ -1,103 +0,0 @@
-<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
-import { mapGetters, mapState } from 'vuex';
-import { s__ } from '~/locale';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
-import { NpmManager, TrackingActions, TrackingLabels } from '../constants';
-
-export default {
- name: 'NpmInstallation',
- components: {
- InstallationTitle,
- CodeInstruction,
- GlLink,
- GlSprintf,
- },
- data() {
- return {
- instructionType: 'npm',
- };
- },
- computed: {
- ...mapState(['npmHelpPath']),
- ...mapGetters(['npmInstallationCommand', 'npmSetupCommand']),
- npmCommand() {
- return this.npmInstallationCommand(NpmManager.NPM);
- },
- npmSetup() {
- return this.npmSetupCommand(NpmManager.NPM);
- },
- yarnCommand() {
- return this.npmInstallationCommand(NpmManager.YARN);
- },
- yarnSetupCommand() {
- return this.npmSetupCommand(NpmManager.YARN);
- },
- showNpm() {
- return this.instructionType === 'npm';
- },
- },
- i18n: {
- helpText: s__(
- 'PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more.',
- ),
- },
- trackingActions: { ...TrackingActions },
- TrackingLabels,
- installOptions: [
- { value: 'npm', label: s__('PackageRegistry|Show NPM commands') },
- { value: 'yarn', label: s__('PackageRegistry|Show Yarn commands') },
- ],
-};
-</script>
-
-<template>
- <div>
- <installation-title
- package-type="npm"
- :options="$options.installOptions"
- @change="instructionType = $event"
- />
-
- <code-instruction
- v-if="showNpm"
- :instruction="npmCommand"
- :copy-text="s__('PackageRegistry|Copy npm command')"
- :tracking-action="$options.trackingActions.COPY_NPM_INSTALL_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
-
- <code-instruction
- v-else
- :instruction="yarnCommand"
- :copy-text="s__('PackageRegistry|Copy yarn command')"
- :tracking-action="$options.trackingActions.COPY_YARN_INSTALL_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
-
- <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
-
- <code-instruction
- v-if="showNpm"
- :instruction="npmSetup"
- :copy-text="s__('PackageRegistry|Copy npm setup command')"
- :tracking-action="$options.trackingActions.COPY_NPM_SETUP_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
-
- <code-instruction
- v-else
- :instruction="yarnSetupCommand"
- :copy-text="s__('PackageRegistry|Copy yarn setup command')"
- :tracking-action="$options.trackingActions.COPY_YARN_SETUP_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
-
- <gl-sprintf :message="$options.i18n.helpText">
- <template #link="{ content }">
- <gl-link :href="npmHelpPath" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </div>
-</template>
diff --git a/app/assets/javascripts/packages/details/components/nuget_installation.vue b/app/assets/javascripts/packages/details/components/nuget_installation.vue
deleted file mode 100644
index d5e64722f24..00000000000
--- a/app/assets/javascripts/packages/details/components/nuget_installation.vue
+++ /dev/null
@@ -1,58 +0,0 @@
-<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
-import { mapGetters, mapState } from 'vuex';
-import { s__ } from '~/locale';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
-import { TrackingActions, TrackingLabels } from '../constants';
-
-export default {
- name: 'NugetInstallation',
- components: {
- InstallationTitle,
- CodeInstruction,
- GlLink,
- GlSprintf,
- },
- computed: {
- ...mapState(['nugetHelpPath']),
- ...mapGetters(['nugetInstallationCommand', 'nugetSetupCommand']),
- },
- i18n: {
- helpText: s__(
- 'PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}.',
- ),
- },
- trackingActions: { ...TrackingActions },
- TrackingLabels,
- installOptions: [{ value: 'nuget', label: s__('PackageRegistry|Show Nuget commands') }],
-};
-</script>
-
-<template>
- <div>
- <installation-title package-type="nuget" :options="$options.installOptions" />
-
- <code-instruction
- :label="s__('PackageRegistry|NuGet Command')"
- :instruction="nugetInstallationCommand"
- :copy-text="s__('PackageRegistry|Copy NuGet Command')"
- :tracking-action="$options.trackingActions.COPY_NUGET_INSTALL_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
- <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
-
- <code-instruction
- :label="s__('PackageRegistry|Add NuGet Source')"
- :instruction="nugetSetupCommand"
- :copy-text="s__('PackageRegistry|Copy NuGet Setup Command')"
- :tracking-action="$options.trackingActions.COPY_NUGET_SETUP_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
- <gl-sprintf :message="$options.i18n.helpText">
- <template #link="{ content }">
- <gl-link :href="nugetHelpPath" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </div>
-</template>
diff --git a/app/assets/javascripts/packages/details/components/package_title.vue b/app/assets/javascripts/packages/details/components/package_title.vue
deleted file mode 100644
index d02a7b3ec27..00000000000
--- a/app/assets/javascripts/packages/details/components/package_title.vue
+++ /dev/null
@@ -1,113 +0,0 @@
-<script>
-/* eslint-disable vue/v-slot-style */
-import { GlIcon, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui';
-import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
-import { mapState, mapGetters } from 'vuex';
-import { numberToHumanSize } from '~/lib/utils/number_utils';
-import { __ } from '~/locale';
-import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
-import TitleArea from '~/vue_shared/components/registry/title_area.vue';
-import timeagoMixin from '~/vue_shared/mixins/timeago';
-import PackageTags from '../../shared/components/package_tags.vue';
-
-export default {
- name: 'PackageTitle',
- components: {
- TitleArea,
- GlIcon,
- GlSprintf,
- PackageTags,
- MetadataItem,
- GlBadge,
- },
- directives: {
- GlTooltip: GlTooltipDirective,
- },
- mixins: [timeagoMixin],
- i18n: {
- packageInfo: __('v%{version} published %{timeAgo}'),
- },
- data() {
- return {
- isDesktop: true,
- };
- },
- computed: {
- ...mapState(['packageEntity', 'packageFiles']),
- ...mapGetters(['packageTypeDisplay', 'packagePipeline', 'packageIcon']),
- hasTagsToDisplay() {
- return Boolean(this.packageEntity.tags && this.packageEntity.tags.length);
- },
- totalSize() {
- return numberToHumanSize(this.packageFiles.reduce((acc, p) => acc + p.size, 0));
- },
- },
- mounted() {
- this.isDesktop = GlBreakpointInstance.isDesktop();
- },
- methods: {
- dynamicSlotName(index) {
- return `metadata-tag${index}`;
- },
- },
-};
-</script>
-
-<template>
- <title-area :title="packageEntity.name" :avatar="packageIcon" data-qa-selector="package_title">
- <template #sub-header>
- <gl-icon name="eye" class="gl-mr-3" />
- <gl-sprintf :message="$options.i18n.packageInfo">
- <template #version>
- {{ packageEntity.version }}
- </template>
-
- <template #timeAgo>
- <span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
- &nbsp;{{ timeFormatted(packageEntity.created_at) }}
- </span>
- </template>
- </gl-sprintf>
- </template>
-
- <template v-if="packageTypeDisplay" #metadata-type>
- <metadata-item data-testid="package-type" icon="package" :text="packageTypeDisplay" />
- </template>
-
- <template #metadata-size>
- <metadata-item data-testid="package-size" icon="disk" :text="totalSize" />
- </template>
-
- <template v-if="packagePipeline" #metadata-pipeline>
- <metadata-item
- data-testid="pipeline-project"
- icon="review-list"
- :text="packagePipeline.project.name"
- :link="packagePipeline.project.web_url"
- />
- </template>
-
- <template v-if="packagePipeline" #metadata-ref>
- <metadata-item data-testid="package-ref" icon="branch" :text="packagePipeline.ref" />
- </template>
-
- <template v-if="isDesktop && hasTagsToDisplay" #metadata-tags>
- <package-tags :tag-display-limit="2" :tags="packageEntity.tags" hide-label />
- </template>
-
- <!-- we need to duplicate the package tags on mobile to ensure proper styling inside the flex wrap -->
- <template
- v-for="(tag, index) in packageEntity.tags"
- v-else-if="hasTagsToDisplay"
- v-slot:[dynamicSlotName(index)]
- >
- <gl-badge :key="index" class="gl-my-1" data-testid="tag-badge" variant="info" size="sm">
- {{ tag.name }}
- </gl-badge>
- </template>
-
- <template #right-actions>
- <slot name="delete-button"></slot>
- </template>
- </title-area>
-</template>
diff --git a/app/assets/javascripts/packages/details/components/pypi_installation.vue b/app/assets/javascripts/packages/details/components/pypi_installation.vue
deleted file mode 100644
index fe4709d5feb..00000000000
--- a/app/assets/javascripts/packages/details/components/pypi_installation.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
-import { mapGetters, mapState } from 'vuex';
-import { s__ } from '~/locale';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
-import { TrackingActions, TrackingLabels } from '../constants';
-
-export default {
- name: 'PyPiInstallation',
- components: {
- InstallationTitle,
- CodeInstruction,
- GlLink,
- GlSprintf,
- },
- computed: {
- ...mapState(['pypiHelpPath']),
- ...mapGetters(['pypiPipCommand', 'pypiSetupCommand']),
- },
- i18n: {
- setupText: s__(
- `PackageRegistry|If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file.`,
- ),
- helpText: s__(
- 'PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}.',
- ),
- },
- trackingActions: { ...TrackingActions },
- TrackingLabels,
- installOptions: [{ value: 'pypi', label: s__('PackageRegistry|Show PyPi commands') }],
-};
-</script>
-
-<template>
- <div>
- <installation-title package-type="pypi" :options="$options.installOptions" />
-
- <code-instruction
- :label="s__('PackageRegistry|Pip Command')"
- :instruction="pypiPipCommand"
- :copy-text="s__('PackageRegistry|Copy Pip command')"
- data-testid="pip-command"
- :tracking-action="$options.trackingActions.COPY_PIP_INSTALL_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
-
- <h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
- <p>
- <gl-sprintf :message="$options.i18n.setupText">
- <template #code="{ content }">
- <code>{{ content }}</code>
- </template>
- </gl-sprintf>
- </p>
-
- <code-instruction
- :instruction="pypiSetupCommand"
- :copy-text="s__('PackageRegistry|Copy .pypirc content')"
- data-testid="pypi-setup-content"
- multiline
- :tracking-action="$options.trackingActions.COPY_PYPI_SETUP_COMMAND"
- :tracking-label="$options.TrackingLabels.CODE_INSTRUCTION"
- />
- <gl-sprintf :message="$options.i18n.helpText">
- <template #link="{ content }">
- <gl-link :href="pypiHelpPath" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </div>
-</template>
diff --git a/app/graphql/types/packages/package_type.rb b/app/graphql/types/packages/package_type.rb
index f3fa79cc08c..45207581348 100644
--- a/app/graphql/types/packages/package_type.rb
+++ b/app/graphql/types/packages/package_type.rb
@@ -6,6 +6,8 @@ module Types
graphql_name 'Package'
description 'Represents a package in the Package Registry. Note that this type is in beta and susceptible to changes'
+ connection_type_class(Types::CountableConnectionType)
+
authorize :read_package
field :id, ::Types::GlobalIDType[::Packages::Package], null: false,
diff --git a/app/models/concerns/issue_available_features.rb b/app/models/concerns/issue_available_features.rb
index 933e8b5f687..209456f8b67 100644
--- a/app/models/concerns/issue_available_features.rb
+++ b/app/models/concerns/issue_available_features.rb
@@ -12,7 +12,8 @@ module IssueAvailableFeatures
{
assignee: %w(issue incident),
confidentiality: %w(issue incident),
- time_tracking: %w(issue incident)
+ time_tracking: %w(issue incident),
+ move_and_clone: %w(issue incident)
}.with_indifferent_access
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index edf5dd760c3..64254d3923c 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -542,6 +542,10 @@ class Issue < ApplicationRecord
issue_type_supports?(:time_tracking)
end
+ def supports_move_and_clone?
+ issue_type_supports?(:move_and_clone)
+ end
+
def email_participants_emails
issue_email_participants.pluck(:email)
end
diff --git a/app/services/issues/clone_service.rb b/app/services/issues/clone_service.rb
index cb42334fe32..c675f957cd7 100644
--- a/app/services/issues/clone_service.rb
+++ b/app/services/issues/clone_service.rb
@@ -8,13 +8,7 @@ module Issues
@target_project = target_project
@with_notes = with_notes
- unless issue.can_clone?(current_user, target_project)
- raise CloneError, s_('CloneIssue|Cannot clone issue due to insufficient permissions!')
- end
-
- if target_project.pending_delete?
- raise CloneError, s_('CloneIssue|Cannot clone issue to target project as it is pending deletion.')
- end
+ verify_can_clone_issue!(issue, target_project)
super(issue, target_project)
@@ -30,6 +24,20 @@ module Issues
attr_reader :target_project
attr_reader :with_notes
+ def verify_can_clone_issue!(issue, target_project)
+ unless issue.supports_move_and_clone?
+ raise CloneError, s_('CloneIssue|Cannot clone issues of \'%{issue_type}\' type.') % { issue_type: issue.issue_type }
+ end
+
+ unless issue.can_clone?(current_user, target_project)
+ raise CloneError, s_('CloneIssue|Cannot clone issue due to insufficient permissions!')
+ end
+
+ if target_project.pending_delete?
+ raise CloneError, s_('CloneIssue|Cannot clone issue to target project as it is pending deletion.')
+ end
+ end
+
def update_new_entity
# we don't call `super` because we want to be able to decide whether or not to copy all comments over.
update_new_entity_description
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index ff78221c941..4418b4eb2bf 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -7,13 +7,7 @@ module Issues
def execute(issue, target_project)
@target_project = target_project
- unless issue.can_move?(current_user, @target_project)
- raise MoveError, s_('MoveIssue|Cannot move issue due to insufficient permissions!')
- end
-
- if @project == @target_project
- raise MoveError, s_('MoveIssue|Cannot move issue to project it originates from!')
- end
+ verify_can_move_issue!(issue, target_project)
super
@@ -32,6 +26,20 @@ module Issues
attr_reader :target_project
+ def verify_can_move_issue!(issue, target_project)
+ unless issue.supports_move_and_clone?
+ raise MoveError, s_('MoveIssue|Cannot move issues of \'%{issue_type}\' type.') % { issue_type: issue.issue_type }
+ end
+
+ unless issue.can_move?(current_user, @target_project)
+ raise MoveError, s_('MoveIssue|Cannot move issue due to insufficient permissions!')
+ end
+
+ if @project == @target_project
+ raise MoveError, s_('MoveIssue|Cannot move issue to project it originates from!')
+ end
+ end
+
def update_service_desk_sent_notifications
return unless original_entity.from_service_desk?
diff --git a/config/feature_flags/development/paginated_tree_graphql_query.yml b/config/feature_flags/development/paginated_tree_graphql_query.yml
index 13096412f25..d56d8fc336c 100644
--- a/config/feature_flags/development/paginated_tree_graphql_query.yml
+++ b/config/feature_flags/development/paginated_tree_graphql_query.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337214
milestone: '14.2'
type: development
group: group::source code
-default_enabled: false
+default_enabled: true
diff --git a/doc/api/environments.md b/doc/api/environments.md
index 5187ac7c1b2..8188e0e7b85 100644
--- a/doc/api/environments.md
+++ b/doc/api/environments.md
@@ -64,6 +64,8 @@ Example of response
"slug": "review-fix-foo-dfjre3",
"external_url": "https://review-fix-foo-dfjre3.gitlab.example.com",
"state": "available",
+ "created_at": "2019-05-25T18:55:13.252Z",
+ "updated_at": "2019-05-27T18:55:13.252Z",
"last_deployment": {
"id": 100,
"iid": 34,
@@ -176,7 +178,9 @@ Example response:
"name": "deploy",
"slug": "deploy",
"external_url": "https://deploy.gitlab.example.com",
- "state": "available"
+ "state": "available",
+ "created_at": "2019-05-25T18:55:13.252Z",
+ "updated_at": "2019-05-27T18:55:13.252Z"
}
```
@@ -210,7 +214,9 @@ Example response:
"name": "staging",
"slug": "staging",
"external_url": "https://staging.gitlab.example.com",
- "state": "available"
+ "state": "available",
+ "created_at": "2019-05-25T18:55:13.252Z",
+ "updated_at": "2019-05-27T18:55:13.252Z"
}
```
@@ -302,6 +308,8 @@ Example response:
"name": "deploy",
"slug": "deploy",
"external_url": "https://deploy.gitlab.example.com",
- "state": "stopped"
+ "state": "stopped",
+ "created_at": "2019-05-25T18:55:13.252Z",
+ "updated_at": "2019-05-27T18:55:13.252Z"
}
```
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 6ffc4de5eff..d2736260e0c 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -6510,6 +6510,7 @@ The connection type for [`Package`](#package).
| Name | Type | Description |
| ---- | ---- | ----------- |
+| <a id="packageconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
| <a id="packageconnectionedges"></a>`edges` | [`[PackageEdge]`](#packageedge) | A list of edges. |
| <a id="packageconnectionnodes"></a>`nodes` | [`[Package]`](#package) | A list of nodes. |
| <a id="packageconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
@@ -13534,7 +13535,7 @@ Returns [`[String!]`](#string).
##### `Repository.paginatedTree`
-Paginated tree of the repository. Available only when feature flag `paginated_tree_graphql_query` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice.
+Paginated tree of the repository. Available only when feature flag `paginated_tree_graphql_query` is enabled. This flag is enabled by default.
Returns [`TreeConnection`](#treeconnection).
diff --git a/doc/development/import_project.md b/doc/development/import_project.md
index b5b8d7129eb..9872aa239dc 100644
--- a/doc/development/import_project.md
+++ b/doc/development/import_project.md
@@ -111,9 +111,9 @@ public folder (for example `/tmp/`) fixes the issue.
##### `Name can contain only letters, digits, emojis ...`
```plaintext
-Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter,
-digit, emoji or '_'. and Path can contain only letters, digits, '_', '-' and '.'. Cannot start
-with '-', end in '.git' or end in '.atom'
+Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. It must start with a letter,
+digit, emoji, or '_', and Path can contain only letters, digits, '_', '-', or '.'. It cannot start
+with '-', end in '.git', or end in '.atom'.
```
The project name specified in `project_path` is not valid for one of the specified reasons.
diff --git a/doc/raketasks/index.md b/doc/raketasks/index.md
index df71b8791f8..65a5a06e26a 100644
--- a/doc/raketasks/index.md
+++ b/doc/raketasks/index.md
@@ -51,3 +51,14 @@ The following Rake tasks are available for use with GitLab:
| [User management](user_management.md) | Perform user management tasks. |
| [Webhooks administration](web_hooks.md) | Maintain project webhooks. |
| [X.509 signatures](x509_signatures.md) | Update X.509 commit signatures, which can be useful if the certificate store changed. |
+
+To list all available Rake tasks:
+
+```shell
+# Omnibus GitLab
+sudo gitlab-rake -vT
+
+# Installations from source
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake -vT RAILS_ENV=production
+```
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index d903ce58982..6754e02f3d3 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -82,6 +82,7 @@ table.supported-languages tr td:last-child {
}
table.supported-languages ul {
+ font-size: 1em;
list-style-type: none;
padding-left: 0px;
margin-bottom: 0px;
diff --git a/doc/user/clusters/management_project_template.md b/doc/user/clusters/management_project_template.md
index 6fd75f5c2ce..305f66c5ec5 100644
--- a/doc/user/clusters/management_project_template.md
+++ b/doc/user/clusters/management_project_template.md
@@ -12,7 +12,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
With a [cluster management project](management_project.md) you can manage
your cluster's deployment and applications through a repository in GitLab.
-The Custer Management project template provides you a baseline to get
+The Cluster Management project template provides you a baseline to get
started and flexibility to customize your project to your cluster's needs.
For instance, you can:
diff --git a/lib/api/entities/environment_basic.rb b/lib/api/entities/environment_basic.rb
index 061d4739874..d9894eac147 100644
--- a/lib/api/entities/environment_basic.rb
+++ b/lib/api/entities/environment_basic.rb
@@ -3,7 +3,7 @@
module API
module Entities
class EnvironmentBasic < Grape::Entity
- expose :id, :name, :slug, :external_url
+ expose :id, :name, :slug, :external_url, :created_at, :updated_at
end
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index a88ef5fe73e..6331aced604 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -220,12 +220,12 @@ module Gitlab
# The character range \p{Alnum} overlaps with \u{00A9}-\u{1f9ff}
# hence the Ruby warning.
# https://gitlab.com/gitlab-org/gitlab/merge_requests/23165#not-easy-fixable
- @project_name_regex ||= /\A[\p{Alnum}\u{00A9}-\u{1f9ff}_][\p{Alnum}\p{Pd}\u{00A9}-\u{1f9ff}_\. ]*\z/.freeze
+ @project_name_regex ||= /\A[\p{Alnum}\u{00A9}-\u{1f9ff}_][\p{Alnum}\p{Pd}\u{002B}\u{00A9}-\u{1f9ff}_\. ]*\z/.freeze
end
def project_name_regex_message
- "can contain only letters, digits, emojis, '_', '.', dash, space. " \
- "It must start with letter, digit, emoji or '_'."
+ "can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. " \
+ "It must start with a letter, digit, emoji, or '_'."
end
def group_name_regex
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2d92e242378..a355063d5ea 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7106,6 +7106,9 @@ msgstr ""
msgid "CloneIssue|Cannot clone issue to target project as it is pending deletion."
msgstr ""
+msgid "CloneIssue|Cannot clone issues of '%{issue_type}' type."
+msgstr ""
+
msgid "Cloned this issue to %{path_to_project}."
msgstr ""
@@ -22168,6 +22171,9 @@ msgstr ""
msgid "MoveIssue|Cannot move issue to project it originates from!"
msgstr ""
+msgid "MoveIssue|Cannot move issues of '%{issue_type}' type."
+msgstr ""
+
msgid "Moved issue to %{label} column in the board."
msgstr ""
diff --git a/spec/features/projects/infrastructure_registry_spec.rb b/spec/features/projects/infrastructure_registry_spec.rb
index 16dd96e6c02..ee35e02b5e8 100644
--- a/spec/features/projects/infrastructure_registry_spec.rb
+++ b/spec/features/projects/infrastructure_registry_spec.rb
@@ -45,10 +45,8 @@ RSpec.describe 'Infrastructure Registry' do
expect(page).to have_css('.packages-app h1[data-testid="title"]', text: terraform_module.name)
- page.within(%Q([name="#{terraform_module.name}"])) do
- expect(page).to have_content('Provision instructions')
- expect(page).to have_content('Registry setup')
- end
+ expect(page).to have_content('Provision instructions')
+ expect(page).to have_content('Registry setup')
end
end
diff --git a/spec/fixtures/api/schemas/graphql/packages/package_details.json b/spec/fixtures/api/schemas/graphql/packages/package_details.json
index 9e8bf7c52d0..9f483d1daf4 100644
--- a/spec/fixtures/api/schemas/graphql/packages/package_details.json
+++ b/spec/fixtures/api/schemas/graphql/packages/package_details.json
@@ -50,6 +50,7 @@
"type": "object",
"additionalProperties": false,
"properties": {
+ "count": { "type": "integer" },
"pageInfo": { "type": "object" },
"edges": { "type": "array" },
"nodes": { "type": "array" }
@@ -72,6 +73,7 @@
"type": "object",
"additionalProperties": false,
"properties": {
+ "count": { "type": "integer" },
"pageInfo": { "type": "object" },
"edges": { "type": "array" },
"nodes": { "type": "array" }
@@ -91,6 +93,7 @@
"type": "object",
"additionalProperties": false,
"properties": {
+ "count": { "type": "integer" },
"pageInfo": { "type": "object" },
"edges": { "type": "array" },
"nodes": { "type": "array" }
@@ -106,6 +109,7 @@
"properties": {
"pageInfo": { "type": "object" },
"edges": { "type": "array" },
+ "count": { "type": "integer" },
"nodes": {
"type": "array",
"items": {
diff --git a/spec/fixtures/api/schemas/public_api/v4/environment.json b/spec/fixtures/api/schemas/public_api/v4/environment.json
index b90bfe8de55..30104adaf5c 100644
--- a/spec/fixtures/api/schemas/public_api/v4/environment.json
+++ b/spec/fixtures/api/schemas/public_api/v4/environment.json
@@ -5,7 +5,9 @@
"name",
"slug",
"external_url",
- "last_deployment"
+ "state",
+ "created_at",
+ "updated_at"
],
"properties": {
"id": { "type": "integer" },
@@ -19,6 +21,9 @@
]
},
"state": { "type": "string" },
+ "created_at": { "type": "string", "format": "date-time" },
+ "updated_at": { "type": "string", "format": "date-time" },
+ "project": { "$ref": "project.json" },
"enable_advanced_logs_querying": { "type": "boolean" },
"logs_api_path": { "type": "string" },
"gitlab_managed_apps_logs_path": { "type": "string" }
diff --git a/spec/frontend/packages/details/components/__snapshots__/conan_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/conan_installation_spec.js.snap
deleted file mode 100644
index a3423e3f4d7..00000000000
--- a/spec/frontend/packages/details/components/__snapshots__/conan_installation_spec.js.snap
+++ /dev/null
@@ -1,36 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ConanInstallation renders all the messages 1`] = `
-<div>
- <installation-title-stub
- options="[object Object]"
- packagetype="conan"
- />
-
- <code-instruction-stub
- copytext="Copy Conan Command"
- instruction="foo/command"
- label="Conan Command"
- trackingaction="copy_conan_command"
- trackinglabel="code_instruction"
- />
-
- <h3
- class="gl-font-lg"
- >
- Registry setup
- </h3>
-
- <code-instruction-stub
- copytext="Copy Conan Setup Command"
- instruction="foo/setup"
- label="Add Conan Remote"
- trackingaction="copy_conan_setup_command"
- trackinglabel="code_instruction"
- />
-
- <gl-sprintf-stub
- message="For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}."
- />
-</div>
-`;
diff --git a/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap
deleted file mode 100644
index 39469bf4fd0..00000000000
--- a/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap
+++ /dev/null
@@ -1,34 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`DependencyRow renders full dependency 1`] = `
-<div
- class="gl-responsive-table-row"
->
- <div
- class="table-section section-50"
- >
- <strong
- class="gl-text-body"
- >
- Test.Dependency
- </strong>
-
- <span
- data-testid="target-framework"
- >
- (.NETStandard2.0)
- </span>
- </div>
-
- <div
- class="table-section section-50 gl-display-flex gl-md-justify-content-end"
- data-testid="version-pattern"
- >
- <span
- class="gl-text-body"
- >
- 2.3.7
- </span>
- </div>
-</div>
-`;
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
deleted file mode 100644
index 8a2793c0010..00000000000
--- a/spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap
+++ /dev/null
@@ -1,112 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`MavenInstallation groovy 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 Groovy DSL install command"
- instruction="foo/gradle/groovy/install"
- label="Gradle Groovy DSL install command"
- trackingaction="copy_gradle_install_command"
- trackinglabel="code_instruction"
- />
-
- <code-instruction-stub
- copytext="Copy add Gradle Groovy DSL repository command"
- instruction="foo/gradle/groovy/add/source"
- label="Add Gradle Groovy DSL repository command"
- multiline="true"
- trackingaction="copy_gradle_add_to_source_command"
- trackinglabel="code_instruction"
- />
-</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],[object Object]"
- packagetype="maven"
- />
-
- <p>
- <gl-sprintf-stub
- message="Copy and paste this inside your %{codeStart}pom.xml%{codeEnd} %{codeStart}dependencies%{codeEnd} block."
- />
- </p>
-
- <code-instruction-stub
- copytext="Copy Maven XML"
- instruction="foo/xml"
- label=""
- multiline="true"
- trackingaction="copy_maven_xml"
- trackinglabel="code_instruction"
- />
-
- <code-instruction-stub
- copytext="Copy Maven command"
- instruction="foo/command"
- label="Maven Command"
- trackingaction="copy_maven_command"
- trackinglabel="code_instruction"
- />
-
- <h3
- class="gl-font-lg"
- >
- Registry setup
- </h3>
-
- <p>
- <gl-sprintf-stub
- message="If you haven't already done so, you will need to add the below to your %{codeStart}pom.xml%{codeEnd} file."
- />
- </p>
-
- <code-instruction-stub
- copytext="Copy Maven registry XML"
- instruction="foo/setup"
- label=""
- multiline="true"
- trackingaction="copy_maven_setup_xml"
- trackinglabel="code_instruction"
- />
-
- <gl-sprintf-stub
- message="For more information on the Maven registry, %{linkStart}see the documentation%{linkEnd}."
- />
-</div>
-`;
diff --git a/spec/frontend/packages/details/components/__snapshots__/npm_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/npm_installation_spec.js.snap
deleted file mode 100644
index 015c7b94dde..00000000000
--- a/spec/frontend/packages/details/components/__snapshots__/npm_installation_spec.js.snap
+++ /dev/null
@@ -1,36 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`NpmInstallation renders all the messages 1`] = `
-<div>
- <installation-title-stub
- options="[object Object],[object Object]"
- packagetype="npm"
- />
-
- <code-instruction-stub
- copytext="Copy npm command"
- instruction="npm i @Test/package"
- label=""
- trackingaction="copy_npm_install_command"
- trackinglabel="code_instruction"
- />
-
- <h3
- class="gl-font-lg"
- >
- Registry setup
- </h3>
-
- <code-instruction-stub
- copytext="Copy npm setup command"
- instruction="echo @Test:registry=undefined/ >> .npmrc"
- label=""
- trackingaction="copy_npm_setup_command"
- trackinglabel="code_instruction"
- />
-
- <gl-sprintf-stub
- message="You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more."
- />
-</div>
-`;
diff --git a/spec/frontend/packages/details/components/__snapshots__/nuget_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/nuget_installation_spec.js.snap
deleted file mode 100644
index 04532743952..00000000000
--- a/spec/frontend/packages/details/components/__snapshots__/nuget_installation_spec.js.snap
+++ /dev/null
@@ -1,36 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`NugetInstallation renders all the messages 1`] = `
-<div>
- <installation-title-stub
- options="[object Object]"
- packagetype="nuget"
- />
-
- <code-instruction-stub
- copytext="Copy NuGet Command"
- instruction="foo/command"
- label="NuGet Command"
- trackingaction="copy_nuget_install_command"
- trackinglabel="code_instruction"
- />
-
- <h3
- class="gl-font-lg"
- >
- Registry setup
- </h3>
-
- <code-instruction-stub
- copytext="Copy NuGet Setup Command"
- instruction="foo/setup"
- label="Add NuGet Source"
- trackingaction="copy_nuget_setup_command"
- trackinglabel="code_instruction"
- />
-
- <gl-sprintf-stub
- message="For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}."
- />
-</div>
-`;
diff --git a/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap
deleted file mode 100644
index 318cea98b92..00000000000
--- a/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap
+++ /dev/null
@@ -1,168 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`PackageTitle renders with tags 1`] = `
-<div
- class="gl-display-flex gl-flex-direction-column"
- data-qa-selector="package_title"
->
- <div
- class="gl-display-flex gl-justify-content-space-between gl-py-3"
- >
- <div
- class="gl-flex-direction-column gl-flex-grow-1"
- >
- <div
- class="gl-display-flex"
- >
- <!---->
-
- <div
- class="gl-display-flex gl-flex-direction-column"
- >
- <h1
- class="gl-font-size-h1 gl-mt-3 gl-mb-2"
- data-testid="title"
- >
- Test package
- </h1>
-
- <div
- class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
- >
- <gl-icon-stub
- class="gl-mr-3"
- name="eye"
- size="16"
- />
-
- <gl-sprintf-stub
- message="v%{version} published %{timeAgo}"
- />
- </div>
- </div>
- </div>
-
- <div
- class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"
- >
- <div
- class="gl-display-flex gl-align-items-center gl-mr-5"
- >
- <metadata-item-stub
- data-testid="package-type"
- icon="package"
- link=""
- size="s"
- text="maven"
- texttooltip=""
- />
- </div>
- <div
- class="gl-display-flex gl-align-items-center gl-mr-5"
- >
- <metadata-item-stub
- data-testid="package-size"
- icon="disk"
- link=""
- size="s"
- text="300 bytes"
- texttooltip=""
- />
- </div>
- <div
- class="gl-display-flex gl-align-items-center gl-mr-5"
- >
- <package-tags-stub
- hidelabel="true"
- tagdisplaylimit="2"
- tags="[object Object],[object Object],[object Object],[object Object]"
- />
- </div>
- </div>
- </div>
-
- <!---->
- </div>
-
- <p />
-</div>
-`;
-
-exports[`PackageTitle renders without tags 1`] = `
-<div
- class="gl-display-flex gl-flex-direction-column"
- data-qa-selector="package_title"
->
- <div
- class="gl-display-flex gl-justify-content-space-between gl-py-3"
- >
- <div
- class="gl-flex-direction-column gl-flex-grow-1"
- >
- <div
- class="gl-display-flex"
- >
- <!---->
-
- <div
- class="gl-display-flex gl-flex-direction-column"
- >
- <h1
- class="gl-font-size-h1 gl-mt-3 gl-mb-2"
- data-testid="title"
- >
- Test package
- </h1>
-
- <div
- class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-mt-1"
- >
- <gl-icon-stub
- class="gl-mr-3"
- name="eye"
- size="16"
- />
-
- <gl-sprintf-stub
- message="v%{version} published %{timeAgo}"
- />
- </div>
- </div>
- </div>
-
- <div
- class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"
- >
- <div
- class="gl-display-flex gl-align-items-center gl-mr-5"
- >
- <metadata-item-stub
- data-testid="package-type"
- icon="package"
- link=""
- size="s"
- text="maven"
- texttooltip=""
- />
- </div>
- <div
- class="gl-display-flex gl-align-items-center gl-mr-5"
- >
- <metadata-item-stub
- data-testid="package-size"
- icon="disk"
- link=""
- size="s"
- text="300 bytes"
- texttooltip=""
- />
- </div>
- </div>
- </div>
-
- <!---->
- </div>
-
- <p />
-</div>
-`;
diff --git a/spec/frontend/packages/details/components/__snapshots__/pypi_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/pypi_installation_spec.js.snap
deleted file mode 100644
index d5bb825d8d1..00000000000
--- a/spec/frontend/packages/details/components/__snapshots__/pypi_installation_spec.js.snap
+++ /dev/null
@@ -1,45 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`PypiInstallation renders all the messages 1`] = `
-<div>
- <installation-title-stub
- options="[object Object]"
- packagetype="pypi"
- />
-
- <code-instruction-stub
- copytext="Copy Pip command"
- data-testid="pip-command"
- instruction="pip install"
- label="Pip Command"
- trackingaction="copy_pip_install_command"
- trackinglabel="code_instruction"
- />
-
- <h3
- class="gl-font-lg"
- >
- Registry setup
- </h3>
-
- <p>
- <gl-sprintf-stub
- message="If you haven't already done so, you will need to add the below to your %{codeStart}.pypirc%{codeEnd} file."
- />
- </p>
-
- <code-instruction-stub
- copytext="Copy .pypirc content"
- data-testid="pypi-setup-content"
- instruction="python setup"
- label=""
- multiline="true"
- trackingaction="copy_pypi_setup_command"
- trackinglabel="code_instruction"
- />
-
- <gl-sprintf-stub
- message="For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}."
- />
-</div>
-`;
diff --git a/spec/frontend/packages/details/components/additional_metadata_spec.js b/spec/frontend/packages/details/components/additional_metadata_spec.js
deleted file mode 100644
index b339aa84348..00000000000
--- a/spec/frontend/packages/details/components/additional_metadata_spec.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import { GlLink, GlSprintf } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import component from '~/packages/details/components/additional_metadata.vue';
-import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
-
-import { mavenPackage, conanPackage, nugetPackage, npmPackage } from '../../mock_data';
-
-describe('Package Additional Metadata', () => {
- let wrapper;
- const defaultProps = {
- packageEntity: { ...mavenPackage },
- };
-
- const mountComponent = (props) => {
- wrapper = shallowMount(component, {
- propsData: { ...defaultProps, ...props },
- stubs: {
- DetailsRow,
- GlSprintf,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
- const findTitle = () => wrapper.find('[data-testid="title"]');
- const findMainArea = () => wrapper.find('[data-testid="main"]');
- const findNugetSource = () => wrapper.find('[data-testid="nuget-source"]');
- const findNugetLicense = () => wrapper.find('[data-testid="nuget-license"]');
- const findConanRecipe = () => wrapper.find('[data-testid="conan-recipe"]');
- const findMavenApp = () => wrapper.find('[data-testid="maven-app"]');
- const findMavenGroup = () => wrapper.find('[data-testid="maven-group"]');
- const findElementLink = (container) => container.find(GlLink);
-
- it('has the correct title', () => {
- mountComponent();
-
- const title = findTitle();
-
- expect(title.exists()).toBe(true);
- expect(title.text()).toBe('Additional Metadata');
- });
-
- describe.each`
- packageEntity | visible | metadata
- ${mavenPackage} | ${true} | ${'maven_metadatum'}
- ${conanPackage} | ${true} | ${'conan_metadatum'}
- ${nugetPackage} | ${true} | ${'nuget_metadatum'}
- ${npmPackage} | ${false} | ${null}
- `('Component visibility', ({ packageEntity, visible, metadata }) => {
- it(`Is ${visible} that the component markup is visible when the package is ${packageEntity.package_type}`, () => {
- mountComponent({ packageEntity });
-
- expect(findTitle().exists()).toBe(visible);
- expect(findMainArea().exists()).toBe(visible);
- });
-
- it(`The component is hidden if ${metadata} is missing`, () => {
- mountComponent({ packageEntity: { ...packageEntity, [metadata]: null } });
-
- expect(findTitle().exists()).toBe(false);
- expect(findMainArea().exists()).toBe(false);
- });
- });
-
- describe('nuget metadata', () => {
- beforeEach(() => {
- mountComponent({ packageEntity: nugetPackage });
- });
-
- it.each`
- name | finderFunction | text | link | icon
- ${'source'} | ${findNugetSource} | ${'Source project located at project-foo-url'} | ${'project_url'} | ${'project'}
- ${'license'} | ${findNugetLicense} | ${'License information located at license-foo-url'} | ${'license_url'} | ${'license'}
- `('$name element', ({ finderFunction, text, link, icon }) => {
- const element = finderFunction();
- expect(element.exists()).toBe(true);
- expect(element.text()).toBe(text);
- expect(element.props('icon')).toBe(icon);
- expect(findElementLink(element).attributes('href')).toBe(nugetPackage.nuget_metadatum[link]);
- });
- });
-
- describe('conan metadata', () => {
- beforeEach(() => {
- mountComponent({ packageEntity: conanPackage });
- });
-
- it.each`
- name | finderFunction | text | icon
- ${'recipe'} | ${findConanRecipe} | ${'Recipe: conan-package/1.0.0@conan+conan-package/stable'} | ${'information-o'}
- `('$name element', ({ finderFunction, text, icon }) => {
- const element = finderFunction();
- expect(element.exists()).toBe(true);
- expect(element.text()).toBe(text);
- expect(element.props('icon')).toBe(icon);
- });
- });
-
- describe('maven metadata', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it.each`
- name | finderFunction | text | icon
- ${'app'} | ${findMavenApp} | ${'App name: test-app'} | ${'information-o'}
- ${'group'} | ${findMavenGroup} | ${'App group: com.test.app'} | ${'information-o'}
- `('$name element', ({ finderFunction, text, icon }) => {
- const element = finderFunction();
- expect(element.exists()).toBe(true);
- expect(element.text()).toBe(text);
- expect(element.props('icon')).toBe(icon);
- });
- });
-});
diff --git a/spec/frontend/packages/details/components/app_spec.js b/spec/frontend/packages/details/components/app_spec.js
index 377e7e05f09..206c5fcf544 100644
--- a/spec/frontend/packages/details/components/app_spec.js
+++ b/spec/frontend/packages/details/components/app_spec.js
@@ -5,28 +5,19 @@ import Vuex from 'vuex';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import stubChildren from 'helpers/stub_children';
-import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue';
import PackagesApp from '~/packages/details/components/app.vue';
-import DependencyRow from '~/packages/details/components/dependency_row.vue';
-import InstallationCommands from '~/packages/details/components/installation_commands.vue';
import PackageFiles from '~/packages/details/components/package_files.vue';
import PackageHistory from '~/packages/details/components/package_history.vue';
-import PackageTitle from '~/packages/details/components/package_title.vue';
import * as getters from '~/packages/details/store/getters';
import PackageListRow from '~/packages/shared/components/package_list_row.vue';
import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
import { TrackingActions } from '~/packages/shared/constants';
import * as SharedUtils from '~/packages/shared/utils';
+import TerraformTitle from '~/packages_and_registries/infrastructure_registry/components/details_title.vue';
+import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
import Tracking from '~/tracking';
-import {
- composerPackage,
- conanPackage,
- mavenPackage,
- mavenFiles,
- npmPackage,
- nugetPackage,
-} from '../../mock_data';
+import { mavenPackage, mavenFiles, npmPackage } from '../../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
@@ -73,7 +64,7 @@ describe('PackagesApp', () => {
store,
stubs: {
...stubChildren(PackagesApp),
- PackageTitle: false,
+ TerraformTitle: false,
TitleArea: false,
GlButton: false,
GlModal: false,
@@ -84,23 +75,18 @@ describe('PackagesApp', () => {
});
}
- const packageTitle = () => wrapper.find(PackageTitle);
- const emptyState = () => wrapper.find(GlEmptyState);
+ const packageTitle = () => wrapper.findComponent(TerraformTitle);
+ const emptyState = () => wrapper.findComponent(GlEmptyState);
const deleteButton = () => wrapper.find('.js-delete-button');
const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' });
const findDeleteFileModal = () => wrapper.find({ ref: 'deleteFileModal' });
const versionsTab = () => wrapper.find('.js-versions-tab > a');
- const packagesLoader = () => wrapper.find(PackagesListLoader);
- const packagesVersionRows = () => wrapper.findAll(PackageListRow);
+ const packagesLoader = () => wrapper.findComponent(PackagesListLoader);
+ const packagesVersionRows = () => wrapper.findAllComponents(PackageListRow);
const noVersionsMessage = () => wrapper.find('[data-testid="no-versions-message"]');
- const dependenciesTab = () => wrapper.find('.js-dependencies-tab > a');
- const dependenciesCountBadge = () => wrapper.find('[data-testid="dependencies-badge"]');
- const noDependenciesMessage = () => wrapper.find('[data-testid="no-dependencies-message"]');
- const dependencyRows = () => wrapper.findAll(DependencyRow);
- const findPackageHistory = () => wrapper.find(PackageHistory);
- const findAdditionalMetadata = () => wrapper.find(AdditionalMetadata);
- const findInstallationCommands = () => wrapper.find(InstallationCommands);
- const findPackageFiles = () => wrapper.find(PackageFiles);
+ const findPackageHistory = () => wrapper.findComponent(PackageHistory);
+ const findTerraformInstallation = () => wrapper.findComponent(TerraformInstallation);
+ const findPackageFiles = () => wrapper.findComponent(PackageFiles);
afterEach(() => {
wrapper.destroy();
@@ -129,21 +115,10 @@ describe('PackagesApp', () => {
expect(findPackageHistory().props('projectName')).toEqual(wrapper.vm.projectName);
});
- it('additional metadata has the right props', () => {
+ it('terraform installation exists', () => {
createComponent();
- expect(findAdditionalMetadata().exists()).toBe(true);
- expect(findAdditionalMetadata().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
- });
-
- it('installation commands has the right props', () => {
- createComponent();
- expect(findInstallationCommands().exists()).toBe(true);
- expect(findInstallationCommands().props('packageEntity')).toEqual(wrapper.vm.packageEntity);
- });
- it('hides the files table if package type is COMPOSER', () => {
- createComponent({ packageEntity: composerPackage });
- expect(findPackageFiles().exists()).toBe(false);
+ expect(findTerraformInstallation().exists()).toBe(true);
});
describe('deleting packages', () => {
@@ -198,45 +173,6 @@ describe('PackagesApp', () => {
});
});
- describe('dependency links', () => {
- it('does not show the dependency links for a non nuget package', () => {
- createComponent();
-
- expect(dependenciesTab().exists()).toBe(false);
- });
-
- it('shows the dependencies tab with 0 count when a nuget package with no dependencies', () => {
- createComponent({
- packageEntity: {
- ...nugetPackage,
- dependency_links: [],
- },
- });
-
- return wrapper.vm.$nextTick(() => {
- const dependenciesBadge = dependenciesCountBadge();
-
- expect(dependenciesTab().exists()).toBe(true);
- expect(dependenciesBadge.exists()).toBe(true);
- expect(dependenciesBadge.text()).toBe('0');
- expect(noDependenciesMessage().exists()).toBe(true);
- });
- });
-
- it('renders the correct number of dependency rows for a nuget package', () => {
- createComponent({ packageEntity: nugetPackage });
-
- return wrapper.vm.$nextTick(() => {
- const dependenciesBadge = dependenciesCountBadge();
-
- expect(dependenciesTab().exists()).toBe(true);
- expect(dependenciesBadge.exists()).toBe(true);
- expect(dependenciesBadge.text()).toBe(nugetPackage.dependency_links.length.toString());
- expect(dependencyRows()).toHaveLength(nugetPackage.dependency_links.length);
- });
- });
- });
-
describe('tracking and delete', () => {
describe('delete package', () => {
const originalReferrer = document.referrer;
@@ -305,9 +241,9 @@ describe('PackagesApp', () => {
});
it('tracking category calls packageTypeToTrackCategory', () => {
- createComponent({ packageEntity: conanPackage });
+ createComponent({ packageEntity: npmPackage });
expect(wrapper.vm.tracking.category).toBe(category);
- expect(utilSpy).toHaveBeenCalledWith('conan');
+ expect(utilSpy).toHaveBeenCalledWith('npm');
});
it(`delete button on delete modal call event with ${TrackingActions.DELETE_PACKAGE}`, () => {
@@ -371,7 +307,7 @@ describe('PackagesApp', () => {
});
it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => {
- createComponent({ packageEntity: conanPackage });
+ createComponent({ packageEntity: npmPackage });
findPackageFiles().vm.$emit('download-file');
expect(eventSpy).toHaveBeenCalledWith(
diff --git a/spec/frontend/packages/details/components/composer_installation_spec.js b/spec/frontend/packages/details/components/composer_installation_spec.js
deleted file mode 100644
index 18d11c7dd57..00000000000
--- a/spec/frontend/packages/details/components/composer_installation_spec.js
+++ /dev/null
@@ -1,133 +0,0 @@
-import { GlSprintf, GlLink } from '@gitlab/ui';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
-import { registryUrl as composerHelpPath } from 'jest/packages/details/mock_data';
-import { composerPackage as packageEntity } from 'jest/packages/mock_data';
-import ComposerInstallation from '~/packages/details/components/composer_installation.vue';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-
-import { TrackingActions } from '~/packages/details/constants';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('ComposerInstallation', () => {
- let wrapper;
- let store;
-
- const composerRegistryIncludeStr = 'foo/registry';
- const composerPackageIncludeStr = 'foo/package';
-
- const createStore = (groupExists = true) => {
- store = new Vuex.Store({
- state: { packageEntity, composerHelpPath },
- getters: {
- composerRegistryInclude: () => composerRegistryIncludeStr,
- composerPackageInclude: () => composerPackageIncludeStr,
- groupExists: () => groupExists,
- },
- });
- };
-
- const findRootNode = () => wrapper.find('[data-testid="root-node"]');
- const findRegistryInclude = () => wrapper.find('[data-testid="registry-include"]');
- const findPackageInclude = () => wrapper.find('[data-testid="package-include"]');
- const findHelpText = () => wrapper.find('[data-testid="help-text"]');
- const findHelpLink = () => wrapper.find(GlLink);
- const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
-
- function createComponent() {
- wrapper = shallowMount(ComposerInstallation, {
- localVue,
- store,
- stubs: {
- GlSprintf,
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('install command switch', () => {
- it('has the installation title component', () => {
- createStore();
- createComponent();
-
- expect(findInstallationTitle().exists()).toBe(true);
- expect(findInstallationTitle().props()).toMatchObject({
- packageType: 'composer',
- options: [{ value: 'composer', label: 'Show Composer commands' }],
- });
- });
- });
-
- describe('registry include command', () => {
- beforeEach(() => {
- createStore();
- createComponent();
- });
-
- it('uses code_instructions', () => {
- const registryIncludeCommand = findRegistryInclude();
- expect(registryIncludeCommand.exists()).toBe(true);
- expect(registryIncludeCommand.props()).toMatchObject({
- instruction: composerRegistryIncludeStr,
- copyText: 'Copy registry include',
- trackingAction: TrackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
- });
- });
-
- it('has the correct title', () => {
- expect(findRegistryInclude().props('label')).toBe('Add composer registry');
- });
- });
-
- describe('package include command', () => {
- beforeEach(() => {
- createStore();
- createComponent();
- });
-
- it('uses code_instructions', () => {
- const registryIncludeCommand = findPackageInclude();
- expect(registryIncludeCommand.exists()).toBe(true);
- expect(registryIncludeCommand.props()).toMatchObject({
- instruction: composerPackageIncludeStr,
- copyText: 'Copy require package include',
- trackingAction: TrackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
- });
- });
-
- it('has the correct title', () => {
- expect(findPackageInclude().props('label')).toBe('Install package version');
- });
-
- it('has the correct help text', () => {
- expect(findHelpText().text()).toBe(
- 'For more information on Composer packages in GitLab, see the documentation.',
- );
- expect(findHelpLink().attributes()).toMatchObject({
- href: composerHelpPath,
- target: '_blank',
- });
- });
- });
-
- describe('root node', () => {
- it('is normally rendered', () => {
- createStore();
- createComponent();
-
- expect(findRootNode().exists()).toBe(true);
- });
-
- it('is not rendered when the group does not exist', () => {
- createStore(false);
- createComponent();
-
- expect(findRootNode().exists()).toBe(false);
- });
- });
-});
diff --git a/spec/frontend/packages/details/components/conan_installation_spec.js b/spec/frontend/packages/details/components/conan_installation_spec.js
deleted file mode 100644
index 78a7d265a21..00000000000
--- a/spec/frontend/packages/details/components/conan_installation_spec.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
-import ConanInstallation from '~/packages/details/components/conan_installation.vue';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
-import { conanPackage as packageEntity } from '../../mock_data';
-import { registryUrl as conanPath } from '../mock_data';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('ConanInstallation', () => {
- let wrapper;
-
- const conanInstallationCommandStr = 'foo/command';
- const conanSetupCommandStr = 'foo/setup';
-
- const store = new Vuex.Store({
- state: {
- packageEntity,
- conanPath,
- },
- getters: {
- conanInstallationCommand: () => conanInstallationCommandStr,
- conanSetupCommand: () => conanSetupCommandStr,
- },
- });
-
- const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
- const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
-
- function createComponent() {
- wrapper = shallowMount(ConanInstallation, {
- localVue,
- store,
- });
- }
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders all the messages', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('install command switch', () => {
- it('has the installation title component', () => {
- expect(findInstallationTitle().exists()).toBe(true);
- expect(findInstallationTitle().props()).toMatchObject({
- packageType: 'conan',
- options: [{ value: 'conan', label: 'Show Conan commands' }],
- });
- });
- });
-
- describe('installation commands', () => {
- it('renders the correct command', () => {
- expect(findCodeInstructions().at(0).props('instruction')).toBe(conanInstallationCommandStr);
- });
- });
-
- describe('setup commands', () => {
- it('renders the correct command', () => {
- expect(findCodeInstructions().at(1).props('instruction')).toBe(conanSetupCommandStr);
- });
- });
-});
diff --git a/spec/frontend/packages/details/components/dependency_row_spec.js b/spec/frontend/packages/details/components/dependency_row_spec.js
deleted file mode 100644
index 7d3ee92908d..00000000000
--- a/spec/frontend/packages/details/components/dependency_row_spec.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import DependencyRow from '~/packages/details/components/dependency_row.vue';
-import { dependencyLinks } from '../../mock_data';
-
-describe('DependencyRow', () => {
- let wrapper;
-
- const { withoutFramework, withoutVersion, fullLink } = dependencyLinks;
-
- function createComponent({ dependencyLink = fullLink } = {}) {
- wrapper = shallowMount(DependencyRow, {
- propsData: {
- dependency: dependencyLink,
- },
- });
- }
-
- const dependencyVersion = () => wrapper.find('[data-testid="version-pattern"]');
- const dependencyFramework = () => wrapper.find('[data-testid="target-framework"]');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('renders', () => {
- it('full dependency', () => {
- createComponent();
-
- expect(wrapper.element).toMatchSnapshot();
- });
- });
-
- describe('version', () => {
- it('does not render any version information when not supplied', () => {
- createComponent({ dependencyLink: withoutVersion });
-
- expect(dependencyVersion().exists()).toBe(false);
- });
-
- it('does render version info when it exists', () => {
- createComponent();
-
- expect(dependencyVersion().exists()).toBe(true);
- expect(dependencyVersion().text()).toBe(fullLink.version_pattern);
- });
- });
-
- describe('target framework', () => {
- it('does not render any framework information when not supplied', () => {
- createComponent({ dependencyLink: withoutFramework });
-
- expect(dependencyFramework().exists()).toBe(false);
- });
-
- it('does render framework info when it exists', () => {
- createComponent();
-
- expect(dependencyFramework().exists()).toBe(true);
- expect(dependencyFramework().text()).toBe(`(${fullLink.target_framework})`);
- });
- });
-});
diff --git a/spec/frontend/packages/details/components/installation_title_spec.js b/spec/frontend/packages/details/components/installation_title_spec.js
deleted file mode 100644
index 14e990d3011..00000000000
--- a/spec/frontend/packages/details/components/installation_title_spec.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import PersistedDropdownSelection from '~/vue_shared/components/registry/persisted_dropdown_selection.vue';
-
-describe('InstallationTitle', () => {
- let wrapper;
-
- const defaultProps = { packageType: 'foo', options: [{ value: 'foo', label: 'bar' }] };
-
- const findPersistedDropdownSelection = () => wrapper.findComponent(PersistedDropdownSelection);
- const findTitle = () => wrapper.find('h3');
-
- function createComponent({ props = {} } = {}) {
- wrapper = shallowMount(InstallationTitle, {
- propsData: {
- ...defaultProps,
- ...props,
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('has a title', () => {
- createComponent();
-
- expect(findTitle().exists()).toBe(true);
- expect(findTitle().text()).toBe('Installation');
- });
-
- describe('persisted dropdown selection', () => {
- it('exists', () => {
- createComponent();
-
- expect(findPersistedDropdownSelection().exists()).toBe(true);
- });
-
- it('has the correct props', () => {
- createComponent();
-
- expect(findPersistedDropdownSelection().props()).toMatchObject({
- storageKey: 'package_foo_installation_instructions',
- options: defaultProps.options,
- });
- });
-
- it('on change event emits a change event', () => {
- createComponent();
-
- findPersistedDropdownSelection().vm.$emit('change', 'baz');
-
- expect(wrapper.emitted('change')).toEqual([['baz']]);
- });
- });
-});
diff --git a/spec/frontend/packages/details/components/installations_commands_spec.js b/spec/frontend/packages/details/components/installations_commands_spec.js
deleted file mode 100644
index 164f9f69741..00000000000
--- a/spec/frontend/packages/details/components/installations_commands_spec.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import ComposerInstallation from '~/packages/details/components/composer_installation.vue';
-import ConanInstallation from '~/packages/details/components/conan_installation.vue';
-import InstallationCommands from '~/packages/details/components/installation_commands.vue';
-
-import MavenInstallation from '~/packages/details/components/maven_installation.vue';
-import NpmInstallation from '~/packages/details/components/npm_installation.vue';
-import NugetInstallation from '~/packages/details/components/nuget_installation.vue';
-import PypiInstallation from '~/packages/details/components/pypi_installation.vue';
-import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue';
-
-import {
- conanPackage,
- mavenPackage,
- npmPackage,
- nugetPackage,
- pypiPackage,
- composerPackage,
- terraformModule,
-} from '../../mock_data';
-
-describe('InstallationCommands', () => {
- let wrapper;
-
- function createComponent(propsData) {
- wrapper = shallowMount(InstallationCommands, {
- propsData,
- });
- }
-
- const npmInstallation = () => wrapper.find(NpmInstallation);
- const mavenInstallation = () => wrapper.find(MavenInstallation);
- const conanInstallation = () => wrapper.find(ConanInstallation);
- const nugetInstallation = () => wrapper.find(NugetInstallation);
- const pypiInstallation = () => wrapper.find(PypiInstallation);
- const composerInstallation = () => wrapper.find(ComposerInstallation);
- const terraformInstallation = () => wrapper.findComponent(TerraformInstallation);
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('installation instructions', () => {
- describe.each`
- packageEntity | selector
- ${conanPackage} | ${conanInstallation}
- ${mavenPackage} | ${mavenInstallation}
- ${npmPackage} | ${npmInstallation}
- ${nugetPackage} | ${nugetInstallation}
- ${pypiPackage} | ${pypiInstallation}
- ${composerPackage} | ${composerInstallation}
- ${terraformModule} | ${terraformInstallation}
- `('renders', ({ packageEntity, selector }) => {
- it(`${packageEntity.package_type} instructions exist`, () => {
- createComponent({ packageEntity });
-
- expect(selector()).toExist();
- });
- });
- });
-});
diff --git a/spec/frontend/packages/details/components/maven_installation_spec.js b/spec/frontend/packages/details/components/maven_installation_spec.js
deleted file mode 100644
index 4972fe70a3d..00000000000
--- a/spec/frontend/packages/details/components/maven_installation_spec.js
+++ /dev/null
@@ -1,184 +0,0 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import Vuex from 'vuex';
-import { registryUrl as mavenPath } from 'jest/packages/details/mock_data';
-import { mavenPackage as packageEntity } from 'jest/packages/mock_data';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import MavenInstallation from '~/packages/details/components/maven_installation.vue';
-import { TrackingActions } from '~/packages/details/constants';
-import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('MavenInstallation', () => {
- let wrapper;
-
- const xmlCodeBlock = 'foo/xml';
- const mavenCommandStr = 'foo/command';
- const mavenSetupXml = 'foo/setup';
- 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: {
- packageEntity,
- mavenPath,
- },
- getters: {
- mavenInstallationXml: () => xmlCodeBlock,
- mavenInstallationCommand: () => mavenCommandStr,
- mavenSetupXml: () => mavenSetupXml,
- gradleGroovyInstalCommand: () => gradleGroovyInstallCommandText,
- gradleGroovyAddSourceCommand: () => gradleGroovyAddSourceCommandText,
- gradleKotlinInstalCommand: () => gradleKotlinInstallCommandText,
- gradleKotlinAddSourceCommand: () => gradleKotlinAddSourceCommandText,
- },
- });
-
- const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
- const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
-
- function createComponent({ data = {} } = {}) {
- wrapper = shallowMount(MavenInstallation, {
- localVue,
- store,
- data() {
- return data;
- },
- });
- }
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('install command switch', () => {
- it('has the installation title component', () => {
- createComponent();
-
- expect(findInstallationTitle().exists()).toBe(true);
- expect(findInstallationTitle().props()).toMatchObject({
- packageType: 'maven',
- options: [
- { value: 'maven', label: 'Maven XML' },
- { value: 'groovy', label: 'Gradle Groovy DSL' },
- { value: 'kotlin', label: 'Gradle Kotlin DSL' },
- ],
- });
- });
-
- it('on change event updates the instructions to show', async () => {
- createComponent();
-
- expect(findCodeInstructions().at(0).props('instruction')).toBe(xmlCodeBlock);
- findInstallationTitle().vm.$emit('change', 'groovy');
-
- await nextTick();
-
- expect(findCodeInstructions().at(0).props('instruction')).toBe(
- gradleGroovyInstallCommandText,
- );
- });
- });
-
- describe('maven', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders all the messages', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('installation commands', () => {
- it('renders the correct xml block', () => {
- expect(findCodeInstructions().at(0).props()).toMatchObject({
- instruction: xmlCodeBlock,
- multiline: true,
- trackingAction: TrackingActions.COPY_MAVEN_XML,
- });
- });
-
- it('renders the correct maven command', () => {
- expect(findCodeInstructions().at(1).props()).toMatchObject({
- instruction: mavenCommandStr,
- multiline: false,
- trackingAction: TrackingActions.COPY_MAVEN_COMMAND,
- });
- });
- });
-
- describe('setup commands', () => {
- it('renders the correct xml block', () => {
- expect(findCodeInstructions().at(2).props()).toMatchObject({
- instruction: mavenSetupXml,
- multiline: true,
- trackingAction: TrackingActions.COPY_MAVEN_SETUP,
- });
- });
- });
- });
-
- describe('groovy', () => {
- beforeEach(() => {
- createComponent({ data: { instructionType: 'groovy' } });
- });
-
- 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: gradleGroovyInstallCommandText,
- multiline: false,
- trackingAction: TrackingActions.COPY_GRADLE_INSTALL_COMMAND,
- });
- });
- });
-
- describe('setup commands', () => {
- it('renders the correct gradle command', () => {
- expect(findCodeInstructions().at(1).props()).toMatchObject({
- instruction: gradleGroovyAddSourceCommandText,
- multiline: true,
- trackingAction: TrackingActions.COPY_GRADLE_ADD_TO_SOURCE_COMMAND,
- });
- });
- });
- });
-
- 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/components/npm_installation_spec.js b/spec/frontend/packages/details/components/npm_installation_spec.js
deleted file mode 100644
index 1c49110bdf8..00000000000
--- a/spec/frontend/packages/details/components/npm_installation_spec.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import Vuex from 'vuex';
-import { registryUrl as nugetPath } from 'jest/packages/details/mock_data';
-import { npmPackage as packageEntity } from 'jest/packages/mock_data';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import NpmInstallation from '~/packages/details/components/npm_installation.vue';
-import { TrackingActions } from '~/packages/details/constants';
-import { npmInstallationCommand, npmSetupCommand } from '~/packages/details/store/getters';
-import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('NpmInstallation', () => {
- let wrapper;
-
- const npmInstallationCommandLabel = 'npm i @Test/package';
- const yarnInstallationCommandLabel = 'yarn add @Test/package';
-
- const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
- const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
-
- function createComponent({ data = {} } = {}) {
- const store = new Vuex.Store({
- state: {
- packageEntity,
- nugetPath,
- },
- getters: {
- npmInstallationCommand,
- npmSetupCommand,
- },
- });
-
- wrapper = shallowMount(NpmInstallation, {
- localVue,
- store,
- data() {
- return data;
- },
- });
- }
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders all the messages', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('install command switch', () => {
- it('has the installation title component', () => {
- expect(findInstallationTitle().exists()).toBe(true);
- expect(findInstallationTitle().props()).toMatchObject({
- packageType: 'npm',
- options: [
- { value: 'npm', label: 'Show NPM commands' },
- { value: 'yarn', label: 'Show Yarn commands' },
- ],
- });
- });
-
- it('on change event updates the instructions to show', async () => {
- createComponent();
-
- expect(findCodeInstructions().at(0).props('instruction')).toBe(npmInstallationCommandLabel);
- findInstallationTitle().vm.$emit('change', 'yarn');
-
- await nextTick();
-
- expect(findCodeInstructions().at(0).props('instruction')).toBe(yarnInstallationCommandLabel);
- });
- });
-
- describe('npm', () => {
- beforeEach(() => {
- createComponent();
- });
- it('renders the correct installation command', () => {
- expect(findCodeInstructions().at(0).props()).toMatchObject({
- instruction: npmInstallationCommandLabel,
- multiline: false,
- trackingAction: TrackingActions.COPY_NPM_INSTALL_COMMAND,
- });
- });
-
- it('renders the correct setup command', () => {
- expect(findCodeInstructions().at(1).props()).toMatchObject({
- instruction: 'echo @Test:registry=undefined/ >> .npmrc',
- multiline: false,
- trackingAction: TrackingActions.COPY_NPM_SETUP_COMMAND,
- });
- });
- });
-
- describe('yarn', () => {
- beforeEach(() => {
- createComponent({ data: { instructionType: 'yarn' } });
- });
-
- it('renders the correct setup command', () => {
- expect(findCodeInstructions().at(0).props()).toMatchObject({
- instruction: yarnInstallationCommandLabel,
- multiline: false,
- trackingAction: TrackingActions.COPY_YARN_INSTALL_COMMAND,
- });
- });
-
- it('renders the correct registry command', () => {
- expect(findCodeInstructions().at(1).props()).toMatchObject({
- instruction: 'echo \\"@Test:registry\\" \\"undefined/\\" >> .yarnrc',
- multiline: false,
- trackingAction: TrackingActions.COPY_YARN_SETUP_COMMAND,
- });
- });
- });
-});
diff --git a/spec/frontend/packages/details/components/nuget_installation_spec.js b/spec/frontend/packages/details/components/nuget_installation_spec.js
deleted file mode 100644
index 8839a8f1108..00000000000
--- a/spec/frontend/packages/details/components/nuget_installation_spec.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
-import { registryUrl as nugetPath } from 'jest/packages/details/mock_data';
-import { nugetPackage as packageEntity } from 'jest/packages/mock_data';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import NugetInstallation from '~/packages/details/components/nuget_installation.vue';
-import { TrackingActions } from '~/packages/details/constants';
-import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('NugetInstallation', () => {
- let wrapper;
-
- const nugetInstallationCommandStr = 'foo/command';
- const nugetSetupCommandStr = 'foo/setup';
-
- const store = new Vuex.Store({
- state: {
- packageEntity,
- nugetPath,
- },
- getters: {
- nugetInstallationCommand: () => nugetInstallationCommandStr,
- nugetSetupCommand: () => nugetSetupCommandStr,
- },
- });
-
- const findCodeInstructions = () => wrapper.findAll(CodeInstructions);
- const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
-
- function createComponent() {
- wrapper = shallowMount(NugetInstallation, {
- localVue,
- store,
- });
- }
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders all the messages', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('install command switch', () => {
- it('has the installation title component', () => {
- expect(findInstallationTitle().exists()).toBe(true);
- expect(findInstallationTitle().props()).toMatchObject({
- packageType: 'nuget',
- options: [{ value: 'nuget', label: 'Show Nuget commands' }],
- });
- });
- });
-
- describe('installation commands', () => {
- it('renders the correct command', () => {
- expect(findCodeInstructions().at(0).props()).toMatchObject({
- instruction: nugetInstallationCommandStr,
- trackingAction: TrackingActions.COPY_NUGET_INSTALL_COMMAND,
- });
- });
- });
-
- describe('setup commands', () => {
- it('renders the correct command', () => {
- expect(findCodeInstructions().at(1).props()).toMatchObject({
- instruction: nugetSetupCommandStr,
- trackingAction: TrackingActions.COPY_NUGET_SETUP_COMMAND,
- });
- });
- });
-});
diff --git a/spec/frontend/packages/details/components/package_title_spec.js b/spec/frontend/packages/details/components/package_title_spec.js
deleted file mode 100644
index 512cec85b40..00000000000
--- a/spec/frontend/packages/details/components/package_title_spec.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
-import PackageTitle from '~/packages/details/components/package_title.vue';
-import PackageTags from '~/packages/shared/components/package_tags.vue';
-import TitleArea from '~/vue_shared/components/registry/title_area.vue';
-import {
- conanPackage,
- mavenFiles,
- mavenPackage,
- mockTags,
- npmFiles,
- npmPackage,
- nugetPackage,
-} from '../../mock_data';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('PackageTitle', () => {
- let wrapper;
- let store;
-
- function createComponent({
- packageEntity = mavenPackage,
- packageFiles = mavenFiles,
- icon = null,
- } = {}) {
- store = new Vuex.Store({
- state: {
- packageEntity,
- packageFiles,
- },
- getters: {
- packageTypeDisplay: ({ packageEntity: { package_type: type } }) => type,
- packagePipeline: ({ packageEntity: { pipeline = null } }) => pipeline,
- packageIcon: () => icon,
- },
- });
-
- wrapper = shallowMount(PackageTitle, {
- localVue,
- store,
- stubs: {
- TitleArea,
- },
- });
- return wrapper.vm.$nextTick();
- }
-
- const findTitleArea = () => wrapper.find(TitleArea);
- const packageType = () => wrapper.find('[data-testid="package-type"]');
- const packageSize = () => wrapper.find('[data-testid="package-size"]');
- const pipelineProject = () => wrapper.find('[data-testid="pipeline-project"]');
- const packageRef = () => wrapper.find('[data-testid="package-ref"]');
- const packageTags = () => wrapper.find(PackageTags);
- const packageBadges = () => wrapper.findAll('[data-testid="tag-badge"]');
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('renders', () => {
- it('without tags', async () => {
- await createComponent();
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('with tags', async () => {
- await createComponent({ packageEntity: { ...mavenPackage, tags: mockTags } });
-
- expect(wrapper.element).toMatchSnapshot();
- });
-
- it('with tags on mobile', async () => {
- jest.spyOn(GlBreakpointInstance, 'isDesktop').mockReturnValue(false);
- await createComponent({ packageEntity: { ...mavenPackage, tags: mockTags } });
- await wrapper.vm.$nextTick();
-
- expect(packageBadges()).toHaveLength(mockTags.length);
- });
- });
-
- describe('package title', () => {
- it('is correctly bound', async () => {
- await createComponent();
-
- expect(findTitleArea().props('title')).toBe('Test package');
- });
- });
-
- describe('package icon', () => {
- const fakeSrc = 'a-fake-src';
-
- it('binds an icon when provided one from vuex', async () => {
- await createComponent({ icon: fakeSrc });
-
- expect(findTitleArea().props('avatar')).toBe(fakeSrc);
- });
-
- it('do not binds an icon when not provided one', async () => {
- await createComponent();
-
- expect(findTitleArea().props('avatar')).toBe(null);
- });
- });
-
- describe.each`
- packageEntity | text
- ${conanPackage} | ${'conan'}
- ${mavenPackage} | ${'maven'}
- ${npmPackage} | ${'npm'}
- ${nugetPackage} | ${'nuget'}
- `(`package type`, ({ packageEntity, text }) => {
- beforeEach(() => createComponent({ packageEntity }));
-
- it(`${packageEntity.package_type} should render from Vuex getters ${text}`, () => {
- expect(packageType().props()).toEqual(expect.objectContaining({ text, icon: 'package' }));
- });
- });
-
- describe('calculates the package size', () => {
- it('correctly calculates when there is only 1 file', async () => {
- await createComponent({ packageEntity: npmPackage, packageFiles: npmFiles });
-
- expect(packageSize().props()).toMatchObject({ text: '200 bytes', icon: 'disk' });
- });
-
- it('correctly calulates when there are multiple files', async () => {
- await createComponent();
-
- expect(packageSize().props('text')).toBe('300 bytes');
- });
- });
-
- describe('package tags', () => {
- it('displays the package-tags component when the package has tags', async () => {
- await createComponent({
- packageEntity: {
- ...npmPackage,
- tags: mockTags,
- },
- });
-
- expect(packageTags().exists()).toBe(true);
- });
-
- it('does not display the package-tags component when there are no tags', async () => {
- await createComponent();
-
- expect(packageTags().exists()).toBe(false);
- });
- });
-
- describe('package ref', () => {
- it('does not display the ref if missing', async () => {
- await createComponent();
-
- expect(packageRef().exists()).toBe(false);
- });
-
- it('correctly shows the package ref if there is one', async () => {
- await createComponent({ packageEntity: npmPackage });
- expect(packageRef().props()).toMatchObject({
- text: npmPackage.pipeline.ref,
- icon: 'branch',
- });
- });
- });
-
- describe('pipeline project', () => {
- it('does not display the project if missing', async () => {
- await createComponent();
-
- expect(pipelineProject().exists()).toBe(false);
- });
-
- it('correctly shows the pipeline project if there is one', async () => {
- await createComponent({ packageEntity: npmPackage });
-
- expect(pipelineProject().props()).toMatchObject({
- text: npmPackage.pipeline.project.name,
- icon: 'review-list',
- link: npmPackage.pipeline.project.web_url,
- });
- });
- });
-});
diff --git a/spec/frontend/packages/details/components/pypi_installation_spec.js b/spec/frontend/packages/details/components/pypi_installation_spec.js
deleted file mode 100644
index 2cec84282d9..00000000000
--- a/spec/frontend/packages/details/components/pypi_installation_spec.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import Vuex from 'vuex';
-import { pypiPackage as packageEntity } from 'jest/packages/mock_data';
-import InstallationTitle from '~/packages/details/components/installation_title.vue';
-import PypiInstallation from '~/packages/details/components/pypi_installation.vue';
-
-const localVue = createLocalVue();
-localVue.use(Vuex);
-
-describe('PypiInstallation', () => {
- let wrapper;
-
- const pipCommandStr = 'pip install';
- const pypiSetupStr = 'python setup';
-
- const store = new Vuex.Store({
- state: {
- packageEntity,
- pypiHelpPath: 'foo',
- },
- getters: {
- pypiPipCommand: () => pipCommandStr,
- pypiSetupCommand: () => pypiSetupStr,
- },
- });
-
- const pipCommand = () => wrapper.find('[data-testid="pip-command"]');
- const setupInstruction = () => wrapper.find('[data-testid="pypi-setup-content"]');
-
- const findInstallationTitle = () => wrapper.findComponent(InstallationTitle);
-
- function createComponent() {
- wrapper = shallowMount(PypiInstallation, {
- localVue,
- store,
- });
- }
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- describe('install command switch', () => {
- it('has the installation title component', () => {
- expect(findInstallationTitle().exists()).toBe(true);
- expect(findInstallationTitle().props()).toMatchObject({
- packageType: 'pypi',
- options: [{ value: 'pypi', label: 'Show PyPi commands' }],
- });
- });
- });
-
- it('renders all the messages', () => {
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe('installation commands', () => {
- it('renders the correct pip command', () => {
- expect(pipCommand().props('instruction')).toBe(pipCommandStr);
- });
- });
-
- describe('setup commands', () => {
- it('renders the correct setup block', () => {
- expect(setupInstruction().props('instruction')).toBe(pypiSetupStr);
- });
- });
-});
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index f1b4e50b1eb..b47f8520514 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -12,22 +12,29 @@ RSpec.describe Gitlab::Regex do
it { is_expected.to match('Dash – is this') }
end
- shared_examples_for 'project/group name regex' do
+ shared_examples_for 'group name regex' do
it_behaves_like 'project/group name chars regex'
it { is_expected.not_to match('?gitlab') }
it { is_expected.not_to match("Users's something") }
end
+ shared_examples_for 'project name regex' do
+ it_behaves_like 'project/group name chars regex'
+ it { is_expected.to match("Gitlab++") }
+ it { is_expected.not_to match('?gitlab') }
+ it { is_expected.not_to match("Users's something") }
+ end
+
describe '.project_name_regex' do
subject { described_class.project_name_regex }
- it_behaves_like 'project/group name regex'
+ it_behaves_like 'project name regex'
end
describe '.group_name_regex' do
subject { described_class.group_name_regex }
- it_behaves_like 'project/group name regex'
+ it_behaves_like 'group name regex'
it 'allows parenthesis' do
is_expected.to match('Group One (Test)')
@@ -51,7 +58,7 @@ RSpec.describe Gitlab::Regex do
describe '.project_name_regex_message' do
subject { described_class.project_name_regex_message }
- it { is_expected.to eq("can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.") }
+ it { is_expected.to eq("can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. It must start with a letter, digit, emoji, or '_'.") }
end
describe '.group_name_regex_message' do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 1747972e8ae..d3d5a429a91 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1505,6 +1505,26 @@ RSpec.describe Issue do
end
end
+ describe '#supports_move_and_clone?' do
+ let_it_be(:project) { create(:project) }
+ let_it_be_with_refind(:issue) { create(:incident, project: project) }
+
+ where(:issue_type, :supports_move_and_clone) do
+ :issue | true
+ :incident | true
+ end
+
+ with_them do
+ before do
+ issue.update!(issue_type: issue_type)
+ end
+
+ it do
+ expect(issue.supports_move_and_clone?).to eq(supports_move_and_clone)
+ end
+ end
+ end
+
describe '#email_participants_emails' do
let_it_be(:issue) { create(:issue) }
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index bc7bb7523c9..5fb24dc91a4 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe API::Environments do
get api("/projects/#{project.id}/environments", user)
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/environments')
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
@@ -167,6 +168,7 @@ RSpec.describe API::Environments do
post api("/projects/#{project.id}/environments", user), params: { name: "mepmep" }
expect(response).to have_gitlab_http_status(:created)
+ expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['name']).to eq('mepmep')
expect(json_response['slug']).to eq('mepmep')
expect(json_response['external']).to be nil
@@ -212,6 +214,7 @@ RSpec.describe API::Environments do
params: { name: 'Mepmep', external_url: url }
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/environment')
expect(json_response['name']).to eq('Mepmep')
expect(json_response['external_url']).to eq(url)
end
@@ -250,7 +253,7 @@ RSpec.describe API::Environments do
expect(response).to have_gitlab_http_status(:forbidden)
end
- it 'returns a 200 for stopped environment' do
+ it 'returns a 204 for stopped environment' do
environment.stop
delete api("/projects/#{project.id}/environments/#{environment.id}", user)
@@ -294,6 +297,7 @@ RSpec.describe API::Environments do
it 'returns a 200' do
expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/environment')
end
it 'actually stops the environment' do
@@ -327,6 +331,7 @@ RSpec.describe API::Environments do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/environment')
+ expect(json_response['last_deployment']).to be_present
end
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 3138dbc56db..4923ef169e8 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -374,7 +374,7 @@ RSpec.describe Projects::UpdateService do
expect(result).to eq({
status: :error,
- message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
+ message: "Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. It must start with a letter, digit, emoji, or '_'."
})
end
end
diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
index af4c9286e7c..f1d8665468c 100644
--- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb
@@ -17,9 +17,11 @@ RSpec.shared_examples 'group and project packages query' do
let(:package_names) { graphql_data_at(resource_type, :packages, :nodes, :name) }
let(:target_shas) { graphql_data_at(resource_type, :packages, :nodes, :metadata, :target_sha) }
let(:packages) { graphql_data_at(resource_type, :packages, :nodes) }
+ let(:packages_count) { graphql_data_at(resource_type, :packages, :count) }
let(:fields) do
<<~QUERY
+ count
nodes {
#{all_graphql_fields_for('packages'.classify, excluded: ['project'])}
metadata { #{query_graphql_fragment('ComposerMetadata')} }
@@ -55,6 +57,10 @@ RSpec.shared_examples 'group and project packages query' do
it 'deals with metadata' do
expect(target_shas).to contain_exactly(composer_metadatum.target_sha)
end
+
+ it 'returns the count of the packages' do
+ expect(packages_count).to eq(4)
+ end
end
context 'when the user does not have access to the resource' do