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-08-19 12:08:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
commitb76ae638462ab0f673e5915986070518dd3f9ad3 (patch)
treebdab0533383b52873be0ec0eb4d3c66598ff8b91 /app/assets/javascripts/packages_and_registries
parent434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff)
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/packages_and_registries')
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue106
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue248
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue87
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue79
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/dependency_row.vue38
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/file_sha.vue41
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue45
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_title.vue38
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue229
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue141
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue75
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue163
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue169
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue134
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue93
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue71
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/constants.js88
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/fragmentTypes.json17
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js23
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql5
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql5
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql111
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/details.js5
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/utils.js40
24 files changed, 1956 insertions, 95 deletions
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue
new file mode 100644
index 00000000000..4d6a1d5462b
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/additional_metadata.vue
@@ -0,0 +1,106 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import {
+ PACKAGE_TYPE_NUGET,
+ PACKAGE_TYPE_CONAN,
+ PACKAGE_TYPE_MAVEN,
+} from '~/packages_and_registries/package_registry/constants';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
+
+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() {
+ return (
+ [PACKAGE_TYPE_NUGET, PACKAGE_TYPE_CONAN, PACKAGE_TYPE_MAVEN].includes(
+ this.packageEntity.packageType,
+ ) && this.packageEntity.metadata
+ );
+ },
+ showNugetMetadata() {
+ return this.packageEntity.packageType === PACKAGE_TYPE_NUGET;
+ },
+ showConanMetadata() {
+ return this.packageEntity.packageType === PACKAGE_TYPE_CONAN;
+ },
+ showMavenMetadata() {
+ return this.packageEntity.packageType === PACKAGE_TYPE_MAVEN;
+ },
+ },
+};
+</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="showNugetMetadata">
+ <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.metadata.projectUrl" target="_blank">{{
+ packageEntity.metadata.projectUrl
+ }}</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.metadata.licenseUrl" target="_blank">{{
+ packageEntity.metadata.licenseUrl
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </details-row>
+ </template>
+
+ <details-row
+ v-else-if="showConanMetadata"
+ icon="information-o"
+ padding="gl-p-4"
+ data-testid="conan-recipe"
+ >
+ <gl-sprintf :message="$options.i18n.recipeText">
+ <template #recipe>{{ packageEntity.metadata.recipe }}</template>
+ </gl-sprintf>
+ </details-row>
+
+ <template v-else-if="showMavenMetadata">
+ <details-row icon="information-o" padding="gl-p-4" dashed data-testid="maven-app">
+ <gl-sprintf :message="$options.i18n.appName">
+ <template #name>
+ <strong>{{ packageEntity.metadata.appName }}</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.metadata.appGroup }}</strong>
+ </template>
+ </gl-sprintf>
+ </details-row>
+ </template>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
index e2a2fb1430d..3d3fa62fd43 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
@@ -1,8 +1,4 @@
<script>
-/*
- * The commented part of this component needs to be re-enabled in the refactor process,
- * See here for more info: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64939
- */
import {
GlBadge,
GlButton,
@@ -14,22 +10,39 @@ import {
GlTabs,
GlSprintf,
} from '@gitlab/ui';
+import createFlash from '~/flash';
+import { convertToGraphQLId } from '~/graphql_shared/utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { objectToQuery } from '~/lib/utils/url_utility';
import { s__, __ } from '~/locale';
-// import AdditionalMetadata from '~/packages/details/components/additional_metadata.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 PackageListRow from '~/packages/shared/components/package_list_row.vue';
-import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
+import { packageTypeToTrackCategory } from '~/packages/shared/utils';
+import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue';
+import DependencyRow from '~/packages_and_registries/package_registry/components/details/dependency_row.vue';
+import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue';
+import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue';
+import PackageHistory from '~/packages_and_registries/package_registry/components/details/package_history.vue';
+import PackageTitle from '~/packages_and_registries/package_registry/components/details/package_title.vue';
+import VersionRow from '~/packages_and_registries/package_registry/components/details/version_row.vue';
import {
- PackageType,
- TrackingActions,
+ PACKAGE_TYPE_NUGET,
+ PACKAGE_TYPE_COMPOSER,
+ DELETE_PACKAGE_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
+ CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
+ PULL_PACKAGE_TRACKING_ACTION,
+ DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION,
SHOW_DELETE_SUCCESS_ALERT,
-} from '~/packages/shared/constants';
-import { packageTypeToTrackCategory } from '~/packages/shared/utils';
+ FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
+ DELETE_PACKAGE_ERROR_MESSAGE,
+ DELETE_PACKAGE_FILE_ERROR_MESSAGE,
+ DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
+} from '~/packages_and_registries/package_registry/constants';
+
+import destroyPackageMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql';
+import destroyPackageFileMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql';
+import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql';
import Tracking from '~/tracking';
export default {
@@ -42,16 +55,13 @@ export default {
GlTab,
GlTabs,
GlSprintf,
- PackageTitle: () => import('~/packages/details/components/package_title.vue'),
- TerraformTitle: () =>
- import('~/packages_and_registries/infrastructure_registry/components/details_title.vue'),
- PackagesListLoader,
- // PackageListRow,
- // DependencyRow,
- // PackageHistory,
- // AdditionalMetadata,
- // InstallationCommands,
- // PackageFiles,
+ PackageTitle,
+ VersionRow,
+ DependencyRow,
+ PackageHistory,
+ AdditionalMetadata,
+ InstallationCommands,
+ PackageFiles,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -59,7 +69,7 @@ export default {
},
mixins: [Tracking.mixin()],
inject: [
- 'titleComponent',
+ 'packageId',
'projectName',
'canDelete',
'svgPath',
@@ -68,72 +78,150 @@ export default {
'projectListUrl',
'groupListUrl',
],
- trackingActions: { ...TrackingActions },
+ trackingActions: {
+ DELETE_PACKAGE_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
+ CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
+ PULL_PACKAGE_TRACKING_ACTION,
+ DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ },
data() {
return {
fileToDelete: null,
packageEntity: {},
};
},
+ apollo: {
+ packageEntity: {
+ query: getPackageDetails,
+ variables() {
+ return this.queryVariables;
+ },
+ update(data) {
+ return data.package;
+ },
+ error(error) {
+ createFlash({
+ message: FETCH_PACKAGE_DETAILS_ERROR_MESSAGE,
+ captureError: true,
+ error,
+ });
+ },
+ },
+ },
computed: {
+ queryVariables() {
+ return {
+ id: convertToGraphQLId('Packages::Package', this.packageId),
+ };
+ },
packageFiles() {
- return this.packageEntity.packageFiles;
+ return this.packageEntity?.packageFiles?.nodes;
},
isLoading() {
- return false;
+ return this.$apollo.queries.packageEntity.loading;
},
isValidPackage() {
- return Boolean(this.packageEntity.name);
+ return this.isLoading || Boolean(this.packageEntity?.name);
},
tracking() {
return {
- category: packageTypeToTrackCategory(this.packageEntity.package_type),
+ category: packageTypeToTrackCategory(this.packageEntity.packageType),
};
},
hasVersions() {
- return this.packageEntity.versions?.length > 0;
+ return this.packageEntity.versions?.nodes?.length > 0;
},
packageDependencies() {
- return this.packageEntity.dependency_links || [];
+ return this.packageEntity.dependencyLinks?.nodes || [];
},
showDependencies() {
- return this.packageEntity.package_type === PackageType.NUGET;
+ return this.packageEntity.packageType === PACKAGE_TYPE_NUGET;
},
showFiles() {
- return this.packageEntity?.package_type !== PackageType.COMPOSER;
+ return this.packageEntity?.packageType !== PACKAGE_TYPE_COMPOSER;
},
},
methods: {
formatSize(size) {
return numberToHumanSize(size);
},
- getPackageVersions() {
- if (!this.packageEntity.versions) {
- // this.fetchPackageVersions();
+ async deletePackage() {
+ const { data } = await this.$apollo.mutate({
+ mutation: destroyPackageMutation,
+ variables: {
+ id: this.packageEntity.id,
+ },
+ });
+
+ if (data?.destroyPackage?.errors[0]) {
+ throw data.destroyPackage.errors[0];
}
},
async confirmPackageDeletion() {
- this.track(TrackingActions.DELETE_PACKAGE);
+ this.track(DELETE_PACKAGE_TRACKING_ACTION);
- await this.deletePackage();
+ try {
+ await this.deletePackage();
- const returnTo =
- !this.groupListUrl || document.referrer.includes(this.projectName)
- ? this.projectListUrl
- : this.groupListUrl; // to avoid security issue url are supplied from backend
+ const returnTo =
+ !this.groupListUrl || document.referrer.includes(this.projectName)
+ ? this.projectListUrl
+ : this.groupListUrl; // to avoid security issue url are supplied from backend
- const modalQuery = objectToQuery({ [SHOW_DELETE_SUCCESS_ALERT]: true });
+ const modalQuery = objectToQuery({ [SHOW_DELETE_SUCCESS_ALERT]: true });
- window.location.replace(`${returnTo}?${modalQuery}`);
+ window.location.replace(`${returnTo}?${modalQuery}`);
+ } catch (error) {
+ createFlash({
+ message: DELETE_PACKAGE_ERROR_MESSAGE,
+ type: 'warning',
+ captureError: true,
+ error,
+ });
+ }
+ },
+ async deletePackageFile(id) {
+ try {
+ const { data } = await this.$apollo.mutate({
+ mutation: destroyPackageFileMutation,
+ variables: {
+ id,
+ },
+ awaitRefetchQueries: true,
+ refetchQueries: [
+ {
+ query: getPackageDetails,
+ variables: this.queryVariables,
+ },
+ ],
+ });
+ if (data?.destroyPackageFile?.errors[0]) {
+ throw data.destroyPackageFile.errors[0];
+ }
+ createFlash({
+ message: DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
+ type: 'success',
+ });
+ } catch (error) {
+ createFlash({
+ message: DELETE_PACKAGE_FILE_ERROR_MESSAGE,
+ type: 'warning',
+ captureError: true,
+ error,
+ });
+ }
},
handleFileDelete(file) {
- this.track(TrackingActions.REQUEST_DELETE_PACKAGE_FILE);
+ this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION);
this.fileToDelete = { ...file };
this.$refs.deleteFileModal.show();
},
confirmFileDelete() {
- this.track(TrackingActions.DELETE_PACKAGE_FILE);
- // this.deletePackageFile(this.fileToDelete.id);
+ this.track(DELETE_PACKAGE_FILE_TRACKING_ACTION);
+ this.deletePackageFile(this.fileToDelete.id);
this.fileToDelete = null;
},
},
@@ -174,60 +262,48 @@ export default {
:description="s__('PackageRegistry|There was a problem fetching the details for this package.')"
:svg-path="svgPath"
/>
-
- <div v-else class="packages-app">
- <component :is="titleComponent">
+ <div v-else-if="!isLoading" class="packages-app">
+ <package-title :package-entity="packageEntity">
<template #delete-button>
<gl-button
v-if="canDelete"
v-gl-modal="'delete-modal'"
- class="js-delete-button"
variant="danger"
category="primary"
data-qa-selector="delete_button"
+ data-testid="delete-package"
>
{{ __('Delete') }}
</gl-button>
</template>
- </component>
+ </package-title>
<gl-tabs>
<gl-tab :title="__('Detail')">
- <div data-qa-selector="package_information_content">
- <!-- <package-history :package-entity="packageEntity" :project-name="projectName" />
+ <div v-if="!isLoading" 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"
- />
+ <installation-commands :package-entity="packageEntity" />
- <additional-metadata :package-entity="packageEntity" /> -->
+ <additional-metadata :package-entity="packageEntity" />
</div>
- <!-- <package-files
+ <package-files
v-if="showFiles"
:package-files="packageFiles"
- :can-delete="canDelete"
@download-file="track($options.trackingActions.PULL_PACKAGE)"
@delete-file="handleFileDelete"
- /> -->
+ />
</gl-tab>
- <gl-tab v-if="showDependencies" title-item-class="js-dependencies-tab">
+ <gl-tab v-if="showDependencies">
<template #title>
<span>{{ __('Dependencies') }}</span>
- <gl-badge size="sm" data-testid="dependencies-badge">{{
- packageDependencies.length
- }}</gl-badge>
+ <gl-badge size="sm">{{ packageDependencies.length }}</gl-badge>
</template>
<template v-if="packageDependencies.length > 0">
- <dependency-row
- v-for="(dep, index) in packageDependencies"
- :key="index"
- :dependency="dep"
- />
+ <dependency-row v-for="dep in packageDependencies" :key="dep.id" :dependency-link="dep" />
</template>
<p v-else class="gl-mt-3" data-testid="no-dependencies-message">
@@ -235,24 +311,9 @@ export default {
</p>
</gl-tab>
- <gl-tab
- :title="__('Other versions')"
- title-item-class="js-versions-tab"
- @click="getPackageVersions"
- >
- <template v-if="isLoading && !hasVersions">
- <packages-list-loader />
- </template>
-
- <template v-else-if="hasVersions">
- <!-- <package-list-row
- v-for="v in packageEntity.versions"
- :key="v.id"
- :package-entity="{ name: packageEntity.name, ...v }"
- :package-link="v.id.toString()"
- :disable-delete="true"
- :show-package-type="false"
- /> -->
+ <gl-tab :title="__('Other versions')" title-item-class="js-versions-tab">
+ <template v-if="hasVersions">
+ <version-row v-for="v in packageEntity.versions.nodes" :key="v.id" :package-entity="v" />
</template>
<p v-else class="gl-mt-3" data-testid="no-versions-message">
@@ -263,8 +324,8 @@ export default {
<gl-modal
ref="deleteModal"
- class="js-delete-modal"
modal-id="delete-modal"
+ data-testid="delete-modal"
:action-primary="$options.modal.packageDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
@primary="confirmPackageDeletion"
@@ -287,6 +348,7 @@ export default {
modal-id="delete-file-modal"
:action-primary="$options.modal.fileDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
+ data-testid="delete-file-modal"
@primary="confirmFileDelete"
@canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE_FILE)"
>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue
new file mode 100644
index 00000000000..cc629ae394c
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue
@@ -0,0 +1,87 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import {
+ TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
+ TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+} from '~/packages_and_registries/package_registry/constants';
+import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
+
+export default {
+ name: 'ComposerInstallation',
+ components: {
+ InstallationTitle,
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ inject: ['composerHelpPath', 'composerConfigRepositoryName', 'composerPath', 'groupListUrl'],
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ composerRegistryInclude() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `composer config repositories.${this.composerConfigRepositoryName} '{"type": "composer", "url": "${this.composerPath}"}'`;
+ },
+ composerPackageInclude() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `composer req ${[this.packageEntity.name]}:${this.packageEntity.version}`;
+ },
+ groupExists() {
+ return this.groupListUrl?.length > 0;
+ },
+ },
+ 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}',
+ ),
+ },
+ tracking: {
+ TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
+ TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+ },
+ 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.tracking.TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
+ data-testid="registry-include"
+ />
+
+ <code-instruction
+ :label="$options.i18n.packageInclude"
+ :instruction="composerPackageInclude"
+ :copy-text="$options.i18n.copyPackageInclude"
+ :tracking-action="$options.tracking.TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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_and_registries/package_registry/components/details/conan_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue
new file mode 100644
index 00000000000..99e27c9d44a
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue
@@ -0,0 +1,79 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import {
+ TRACKING_ACTION_COPY_CONAN_COMMAND,
+ TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+} from '~/packages_and_registries/package_registry/constants';
+import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
+
+export default {
+ name: 'ConanInstallation',
+ components: {
+ InstallationTitle,
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ inject: ['conanHelpPath', 'conanPath'],
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ conanInstallationCommand() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `conan install ${this.packageEntity.name} --remote=gitlab`;
+ },
+ conanSetupCommand() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `conan remote add gitlab ${this.conanPath}`;
+ },
+ },
+ i18n: {
+ helpText: s__(
+ 'PackageRegistry|For more information on the Conan registry, %{linkStart}see the documentation%{linkEnd}.',
+ ),
+ },
+ tracking: {
+ TRACKING_ACTION_COPY_CONAN_COMMAND,
+ TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+ },
+
+ 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.tracking.TRACKING_ACTION_COPY_CONAN_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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.tracking.TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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_and_registries/package_registry/components/details/dependency_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/dependency_row.vue
new file mode 100644
index 00000000000..95236eea0b5
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/dependency_row.vue
@@ -0,0 +1,38 @@
+<script>
+export default {
+ name: 'DependencyRow',
+ props: {
+ dependencyLink: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ showVersion() {
+ return Boolean(this.dependencyLink.dependency?.versionPattern);
+ },
+ showTargetFramework() {
+ return Boolean(this.dependencyLink.metadata?.targetFramework);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-responsive-table-row">
+ <div class="table-section section-50">
+ <strong class="gl-text-body">{{ dependencyLink.dependency.name }}</strong>
+ <span v-if="showTargetFramework" data-testid="target-framework">
+ ({{ dependencyLink.metadata.targetFramework }})
+ </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">{{ dependencyLink.dependency.versionPattern }}</span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/file_sha.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/file_sha.vue
new file mode 100644
index 00000000000..a25839be7e1
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/file_sha.vue
@@ -0,0 +1,41 @@
+<script>
+import { s__ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
+
+export default {
+ name: 'FileSha',
+ components: {
+ DetailsRow,
+ ClipboardButton,
+ },
+ props: {
+ sha: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ },
+ i18n: {
+ copyButtonTitle: s__('PackageRegistry|Copy SHA'),
+ },
+};
+</script>
+
+<template>
+ <details-row dashed>
+ <div class="gl-px-4">
+ {{ title }}:
+ {{ sha }}
+ <clipboard-button
+ :text="sha"
+ :title="$options.i18n.copyButtonTitle"
+ category="tertiary"
+ size="small"
+ />
+ </div>
+ </details-row>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue
new file mode 100644
index 00000000000..122d444e859
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_commands.vue
@@ -0,0 +1,45 @@
+<script>
+import {
+ PACKAGE_TYPE_CONAN,
+ PACKAGE_TYPE_MAVEN,
+ PACKAGE_TYPE_NPM,
+ PACKAGE_TYPE_NUGET,
+ PACKAGE_TYPE_PYPI,
+ PACKAGE_TYPE_COMPOSER,
+} from '~/packages_and_registries/package_registry/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: {
+ [PACKAGE_TYPE_CONAN]: ConanInstallation,
+ [PACKAGE_TYPE_MAVEN]: MavenInstallation,
+ [PACKAGE_TYPE_NPM]: NpmInstallation,
+ [PACKAGE_TYPE_NUGET]: NugetInstallation,
+ [PACKAGE_TYPE_PYPI]: PypiInstallation,
+ [PACKAGE_TYPE_COMPOSER]: ComposerInstallation,
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ installationComponent() {
+ return this.$options.components[this.packageEntity.packageType];
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="installationComponent">
+ <component :is="installationComponent" :package-entity="packageEntity" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_title.vue
new file mode 100644
index 00000000000..43133bf7825
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/installation_title.vue
@@ -0,0 +1,38 @@
+<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_and_registries/package_registry/components/details/maven_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue
new file mode 100644
index 00000000000..2070f0bbca0
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue
@@ -0,0 +1,229 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import {
+ TRACKING_ACTION_COPY_MAVEN_XML,
+ TRACKING_ACTION_COPY_MAVEN_COMMAND,
+ TRACKING_ACTION_COPY_MAVEN_SETUP,
+ TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND,
+ TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+ TRACKING_LABEL_MAVEN_INSTALLATION,
+} from '~/packages_and_registries/package_registry/constants';
+import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
+
+export default {
+ name: 'MavenInstallation',
+ components: {
+ InstallationTitle,
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ inject: ['mavenHelpPath', 'mavenPath'],
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ instructionType: 'maven',
+ };
+ },
+ computed: {
+ appGroup() {
+ return this.packageEntity.metadata.appGroup;
+ },
+ appName() {
+ return this.packageEntity.metadata.appName;
+ },
+ appVersion() {
+ return this.packageEntity.metadata.appVersion;
+ },
+ mavenInstallationXml() {
+ return `<dependency>
+ <groupId>${this.appGroup}</groupId>
+ <artifactId>${this.appName}</artifactId>
+ <version>${this.appVersion}</version>
+</dependency>`;
+ },
+
+ mavenInstallationCommand() {
+ return `mvn dependency:get -Dartifact=${this.appGroup}:${this.appName}:${this.appVersion}`;
+ },
+
+ mavenSetupXml() {
+ return `<repositories>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>${this.mavenPath}</url>
+ </repository>
+</repositories>
+
+<distributionManagement>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>${this.mavenPath}</url>
+ </repository>
+
+ <snapshotRepository>
+ <id>gitlab-maven</id>
+ <url>${this.mavenPath}</url>
+ </snapshotRepository>
+</distributionManagement>`;
+ },
+
+ gradleGroovyInstalCommand() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `implementation '${this.appGroup}:${this.appName}:${this.appVersion}'`;
+ },
+
+ gradleGroovyAddSourceCommand() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `maven {
+ url '${this.mavenPath}'
+}`;
+ },
+
+ gradleKotlinInstalCommand() {
+ return `implementation("${this.appGroup}:${this.appName}:${this.appVersion}")`;
+ },
+
+ gradleKotlinAddSourceCommand() {
+ return `maven("${this.mavenPath}")`;
+ },
+ 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}.',
+ ),
+ },
+ tracking: {
+ TRACKING_ACTION_COPY_MAVEN_XML,
+ TRACKING_ACTION_COPY_MAVEN_COMMAND,
+ TRACKING_ACTION_COPY_MAVEN_SETUP,
+ TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND,
+ TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+ TRACKING_LABEL_MAVEN_INSTALLATION,
+ },
+
+ 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.tracking.TRACKING_ACTION_COPY_MAVEN_XML"
+ :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
+ multiline
+ />
+
+ <code-instruction
+ :label="s__('PackageRegistry|Maven Command')"
+ :instruction="mavenInstallationCommand"
+ :copy-text="s__('PackageRegistry|Copy Maven command')"
+ :tracking-action="$options.tracking.TRACKING_ACTION_COPY_MAVEN_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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.tracking.TRACKING_ACTION_COPY_MAVEN_SETUP"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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.tracking.TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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.tracking.TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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.tracking.TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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.tracking.TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
+ multiline
+ />
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue
new file mode 100644
index 00000000000..47081e23318
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue
@@ -0,0 +1,141 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import {
+ TRACKING_ACTION_COPY_NPM_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_NPM_SETUP_COMMAND,
+ TRACKING_ACTION_COPY_YARN_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_YARN_SETUP_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+ NPM_PACKAGE_MANAGER,
+ YARN_PACKAGE_MANAGER,
+} from '~/packages_and_registries/package_registry/constants';
+import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
+
+export default {
+ name: 'NpmInstallation',
+ components: {
+ InstallationTitle,
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ inject: ['npmHelpPath', 'npmPath'],
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ instructionType: NPM_PACKAGE_MANAGER,
+ };
+ },
+ computed: {
+ npmCommand() {
+ return this.npmInstallationCommand(NPM_PACKAGE_MANAGER);
+ },
+ npmSetup() {
+ return this.npmSetupCommand(NPM_PACKAGE_MANAGER);
+ },
+ yarnCommand() {
+ return this.npmInstallationCommand(YARN_PACKAGE_MANAGER);
+ },
+ yarnSetupCommand() {
+ return this.npmSetupCommand(YARN_PACKAGE_MANAGER);
+ },
+ showNpm() {
+ return this.instructionType === NPM_PACKAGE_MANAGER;
+ },
+ },
+ methods: {
+ npmInstallationCommand(type) {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ const instruction = type === NPM_PACKAGE_MANAGER ? 'npm i' : 'yarn add';
+
+ return `${instruction} ${this.packageEntity.name}`;
+ },
+ npmSetupCommand(type) {
+ const scope = this.packageEntity.name.substring(0, this.packageEntity.name.indexOf('/'));
+
+ if (type === NPM_PACKAGE_MANAGER) {
+ return `echo ${scope}:registry=${this.npmPath}/ >> .npmrc`;
+ }
+
+ return `echo \\"${scope}:registry\\" \\"${this.npmPath}/\\" >> .yarnrc`;
+ },
+ },
+ packageManagers: {
+ NPM_PACKAGE_MANAGER,
+ },
+ tracking: {
+ TRACKING_ACTION_COPY_NPM_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_NPM_SETUP_COMMAND,
+ TRACKING_ACTION_COPY_YARN_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_YARN_SETUP_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+ },
+ i18n: {
+ helpText: s__(
+ 'PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more.',
+ ),
+ },
+ installOptions: [
+ { value: NPM_PACKAGE_MANAGER, label: s__('PackageRegistry|Show NPM commands') },
+ { value: YARN_PACKAGE_MANAGER, label: s__('PackageRegistry|Show Yarn commands') },
+ ],
+};
+</script>
+
+<template>
+ <div>
+ <installation-title
+ :package-type="$options.packageManagers.NPM_PACKAGE_MANAGER"
+ :options="$options.installOptions"
+ @change="instructionType = $event"
+ />
+
+ <code-instruction
+ v-if="showNpm"
+ :instruction="npmCommand"
+ :copy-text="s__('PackageRegistry|Copy npm command')"
+ :tracking-action="$options.tracking.TRACKING_ACTION_COPY_NPM_INSTALL_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
+ />
+
+ <code-instruction
+ v-else
+ :instruction="yarnCommand"
+ :copy-text="s__('PackageRegistry|Copy yarn command')"
+ :tracking-action="$options.tracking.TRACKING_ACTION_COPY_YARN_INSTALL_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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.tracking.TRACKING_ACTION_COPY_NPM_SETUP_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_CODE_INSTRUCTION"
+ />
+
+ <code-instruction
+ v-else
+ :instruction="yarnSetupCommand"
+ :copy-text="s__('PackageRegistry|Copy yarn setup command')"
+ :tracking-action="$options.tracking.TRACKING_ACTION_COPY_YARN_SETUP_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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_and_registries/package_registry/components/details/nuget_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue
new file mode 100644
index 00000000000..2e9991b7be5
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue
@@ -0,0 +1,75 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import {
+ TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+} from '~/packages_and_registries/package_registry/constants';
+import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
+
+export default {
+ name: 'NugetInstallation',
+ components: {
+ InstallationTitle,
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ inject: ['nugetHelpPath', 'nugetPath'],
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ nugetInstallationCommand() {
+ return `nuget install ${this.packageEntity.name} -Source "GitLab"`;
+ },
+ nugetSetupCommand() {
+ return `nuget source Add -Name "GitLab" -Source "${this.nugetPath}" -UserName <your_username> -Password <your_token>`;
+ },
+ },
+ tracking: {
+ TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+ },
+ i18n: {
+ helpText: s__(
+ 'PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}.',
+ ),
+ },
+ 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.tracking.TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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.tracking.TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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_and_registries/package_registry/components/details/package_files.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
new file mode 100644
index 00000000000..bf7fe6fb91b
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
@@ -0,0 +1,163 @@
+<script>
+import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon, GlButton } from '@gitlab/ui';
+import { last } from 'lodash';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { __ } from '~/locale';
+import FileSha from '~/packages_and_registries/package_registry/components/details/file_sha.vue';
+import Tracking from '~/tracking';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ name: 'PackageFiles',
+ components: {
+ GlLink,
+ GlTable,
+ GlIcon,
+ GlDropdown,
+ GlDropdownItem,
+ GlButton,
+ FileIcon,
+ TimeAgoTooltip,
+ FileSha,
+ },
+ mixins: [Tracking.mixin()],
+ inject: ['canDelete'],
+ props: {
+ packageFiles: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+ computed: {
+ filesTableRows() {
+ return this.packageFiles.map((pf) => ({
+ ...pf,
+ size: this.formatSize(pf.size),
+ pipeline: last(pf.pipelines),
+ }));
+ },
+ showCommitColumn() {
+ // note that this is always false for now since we do not return
+ // pipelines associated to files for performance concerns
+ return this.filesTableRows.some((row) => Boolean(row.pipeline?.id));
+ },
+ filesTableHeaderFields() {
+ return [
+ {
+ key: 'name',
+ label: __('Name'),
+ },
+ {
+ key: 'commit',
+ label: __('Commit'),
+ hide: !this.showCommitColumn,
+ },
+ {
+ key: 'size',
+ label: __('Size'),
+ },
+ {
+ key: 'created',
+ label: __('Created'),
+ class: 'gl-text-right',
+ },
+ {
+ key: 'actions',
+ label: '',
+ hide: !this.canDelete,
+ class: 'gl-text-right',
+ tdClass: 'gl-w-4',
+ },
+ ].filter((c) => !c.hide);
+ },
+ },
+ methods: {
+ formatSize(size) {
+ return numberToHumanSize(size);
+ },
+ hasDetails(item) {
+ return item.fileSha256 || item.fileMd5 || item.fileSha1;
+ },
+ },
+ i18n: {
+ deleteFile: __('Delete file'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h3 class="gl-font-lg gl-mt-5">{{ __('Files') }}</h3>
+ <gl-table
+ :fields="filesTableHeaderFields"
+ :items="filesTableRows"
+ :tbody-tr-attr="{ 'data-testid': 'file-row' }"
+ >
+ <template #cell(name)="{ item, toggleDetails, detailsShowing }">
+ <gl-button
+ v-if="hasDetails(item)"
+ :icon="detailsShowing ? 'angle-up' : 'angle-down'"
+ :aria-label="detailsShowing ? __('Collapse') : __('Expand')"
+ category="tertiary"
+ size="small"
+ @click="toggleDetails"
+ />
+ <gl-link
+ :href="item.downloadPath"
+ class="gl-text-gray-500"
+ data-testid="download-link"
+ @click="$emit('download-file')"
+ >
+ <file-icon
+ :file-name="item.fileName"
+ css-classes="gl-relative file-icon"
+ class="gl-mr-1 gl-relative"
+ />
+ <span>{{ item.fileName }}</span>
+ </gl-link>
+ </template>
+
+ <template #cell(commit)="{ item }">
+ <gl-link
+ v-if="item.pipeline && item.pipeline"
+ :href="item.pipeline.commitPath"
+ class="gl-text-gray-500"
+ data-testid="commit-link"
+ >{{ item.pipeline.sha }}</gl-link
+ >
+ </template>
+
+ <template #cell(created)="{ item }">
+ <time-ago-tooltip :time="item.createdAt" />
+ </template>
+
+ <template #cell(actions)="{ item }">
+ <gl-dropdown category="tertiary" right>
+ <template #button-content>
+ <gl-icon name="ellipsis_v" />
+ </template>
+ <gl-dropdown-item data-testid="delete-file" @click="$emit('delete-file', item)">
+ {{ $options.i18n.deleteFile }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </template>
+
+ <template #row-details="{ item }">
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100"
+ >
+ <file-sha
+ v-if="item.fileSha256"
+ data-testid="sha-256"
+ title="SHA-256"
+ :sha="item.fileSha256"
+ />
+ <file-sha v-if="item.fileMd5" data-testid="md5" title="MD5" :sha="item.fileMd5" />
+ <file-sha v-if="item.fileSha1" data-testid="sha-1" title="SHA-1" :sha="item.fileSha1" />
+ </div>
+ </template>
+ </gl-table>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
new file mode 100644
index 00000000000..af4a984add4
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
@@ -0,0 +1,169 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { first } from 'lodash';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { truncateSha } from '~/lib/utils/text_utility';
+import { s__, n__ } from '~/locale';
+import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
+import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ name: 'PackageHistory',
+ i18n: {
+ createdOn: s__('PackageRegistry|%{name} version %{version} was first created %{datetime}'),
+ createdByCommitText: s__('PackageRegistry|Created by commit %{link} on branch %{branch}'),
+ createdByPipelineText: s__(
+ 'PackageRegistry|Built by pipeline %{link} triggered %{datetime} by %{author}',
+ ),
+ publishText: s__('PackageRegistry|Published to the %{project} Package Registry %{datetime}'),
+ combinedUpdateText: s__(
+ 'PackageRegistry|Package updated by commit %{link} on branch %{branch}, built by pipeline %{pipeline}, and published to the registry %{datetime}',
+ ),
+ archivedPipelineMessageSingular: s__('PackageRegistry|Package has %{number} archived update'),
+ archivedPipelineMessagePlural: s__('PackageRegistry|Package has %{number} archived updates'),
+ },
+ components: {
+ GlLink,
+ GlSprintf,
+ HistoryItem,
+ TimeAgoTooltip,
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ projectName: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ showDescription: false,
+ };
+ },
+ computed: {
+ pipelines() {
+ return this.packageEntity?.pipelines?.nodes || [];
+ },
+ firstPipeline() {
+ return first(this.pipelines);
+ },
+ lastPipelines() {
+ return this.pipelines.slice(1).slice(-HISTORY_PIPELINES_LIMIT);
+ },
+ showPipelinesInfo() {
+ return Boolean(this.firstPipeline?.id);
+ },
+ archiviedLines() {
+ return Math.max(this.pipelines.length - HISTORY_PIPELINES_LIMIT - 1, 0);
+ },
+ archivedPipelineMessage() {
+ return n__(
+ this.$options.i18n.archivedPipelineMessageSingular,
+ this.$options.i18n.archivedPipelineMessagePlural,
+ this.archiviedLines,
+ );
+ },
+ },
+ methods: {
+ truncate(value) {
+ return truncateSha(value);
+ },
+ convertToBaseId(value) {
+ return getIdFromGraphQLId(value);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="issuable-discussion">
+ <h3 class="gl-font-lg" data-testid="title">{{ __('History') }}</h3>
+ <ul class="timeline main-notes-list notes gl-mb-4" data-testid="timeline">
+ <history-item icon="clock" data-testid="created-on">
+ <gl-sprintf :message="$options.i18n.createdOn">
+ <template #name>
+ <strong>{{ packageEntity.name }}</strong>
+ </template>
+ <template #version>
+ <strong>{{ packageEntity.version }}</strong>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="packageEntity.createdAt" />
+ </template>
+ </gl-sprintf>
+ </history-item>
+
+ <template v-if="showPipelinesInfo">
+ <!-- FIRST PIPELINE BLOCK -->
+ <history-item icon="commit" data-testid="first-pipeline-commit">
+ <gl-sprintf :message="$options.i18n.createdByCommitText">
+ <template #link>
+ <gl-link :href="firstPipeline.commitPath">#{{ truncate(firstPipeline.sha) }}</gl-link>
+ </template>
+ <template #branch>
+ <strong>{{ firstPipeline.ref }}</strong>
+ </template>
+ </gl-sprintf>
+ </history-item>
+ <history-item icon="pipeline" data-testid="first-pipeline-pipeline">
+ <gl-sprintf :message="$options.i18n.createdByPipelineText">
+ <template #link>
+ <gl-link :href="firstPipeline.path">#{{ convertToBaseId(firstPipeline.id) }}</gl-link>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="firstPipeline.createdAt" />
+ </template>
+ <template #author>{{ firstPipeline.user.name }}</template>
+ </gl-sprintf>
+ </history-item>
+ </template>
+
+ <!-- PUBLISHED LINE -->
+ <history-item icon="package" data-testid="published">
+ <gl-sprintf :message="$options.i18n.publishText">
+ <template #project>
+ <strong>{{ projectName }}</strong>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="packageEntity.createdAt" />
+ </template>
+ </gl-sprintf>
+ </history-item>
+
+ <history-item v-if="archiviedLines" icon="history" data-testid="archived">
+ <gl-sprintf :message="archivedPipelineMessage">
+ <template #number>
+ <strong>{{ archiviedLines }}</strong>
+ </template>
+ </gl-sprintf>
+ </history-item>
+
+ <!-- PIPELINES LIST ENTRIES -->
+ <history-item
+ v-for="pipeline in lastPipelines"
+ :key="pipeline.id"
+ icon="pencil"
+ data-testid="pipeline-entry"
+ >
+ <gl-sprintf :message="$options.i18n.combinedUpdateText">
+ <template #link>
+ <gl-link :href="pipeline.commitPath">#{{ truncate(pipeline.sha) }}</gl-link>
+ </template>
+ <template #branch>
+ <strong>{{ pipeline.ref }}</strong>
+ </template>
+ <template #pipeline>
+ <gl-link :href="pipeline.path">#{{ convertToBaseId(pipeline.id) }}</gl-link>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="pipeline.createdAt" />
+ </template>
+ </gl-sprintf>
+ </history-item>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue
new file mode 100644
index 00000000000..65547af3913
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue
@@ -0,0 +1,134 @@
+<script>
+import { GlIcon, GlSprintf, GlBadge } from '@gitlab/ui';
+import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { __ } from '~/locale';
+import PackageTags from '~/packages/shared/components/package_tags.vue';
+import { PACKAGE_TYPE_NUGET } from '~/packages_and_registries/package_registry/constants';
+import { getPackageTypeLabel } from '~/packages_and_registries/package_registry/utils';
+import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ name: 'PackageTitle',
+ components: {
+ TitleArea,
+ GlIcon,
+ GlSprintf,
+ PackageTags,
+ MetadataItem,
+ GlBadge,
+ TimeAgoTooltip,
+ },
+ i18n: {
+ packageInfo: __('v%{version} published %{timeAgo}'),
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ isDesktop: true,
+ };
+ },
+ computed: {
+ packageTypeDisplay() {
+ return getPackageTypeLabel(this.packageEntity.packageType);
+ },
+ packagePipeline() {
+ return this.packageEntity.pipelines?.nodes[0];
+ },
+ packageIcon() {
+ if (this.packageEntity.packageType === PACKAGE_TYPE_NUGET) {
+ return this.packageEntity.metadata?.iconUrl || null;
+ }
+ return null;
+ },
+ hasTagsToDisplay() {
+ return Boolean(this.packageEntity.tags?.nodes && this.packageEntity.tags?.nodes.length);
+ },
+ totalSize() {
+ return this.packageEntity.packageFiles
+ ? numberToHumanSize(
+ this.packageEntity.packageFiles.nodes.reduce((acc, p) => acc + Number(p.size), 0),
+ )
+ : '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" />
+ <span data-testid="sub-header">
+ <gl-sprintf :message="$options.i18n.packageInfo">
+ <template #version>
+ {{ packageEntity.version }}
+ </template>
+
+ <template #timeAgo>
+ <time-ago-tooltip
+ v-if="packageEntity.createdAt"
+ class="gl-ml-2"
+ :time="packageEntity.createdAt"
+ />
+ </template>
+ </gl-sprintf>
+ </span>
+ </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.webUrl"
+ />
+ </template>
+
+ <template v-if="packagePipeline && packagePipeline.ref" #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.nodes" 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.nodes"
+ v-else-if="hasTagsToDisplay"
+ #[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_and_registries/package_registry/components/details/pypi_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue
new file mode 100644
index 00000000000..669adab9df6
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue
@@ -0,0 +1,93 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+
+import { s__ } from '~/locale';
+import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
+import {
+ TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+} from '~/packages_and_registries/package_registry/constants';
+import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
+
+export default {
+ name: 'PyPiInstallation',
+ components: {
+ InstallationTitle,
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ inject: ['pypiHelpPath', 'pypiPath', 'pypiSetupPath'],
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ pypiPipCommand() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return `pip install ${this.packageEntity.name} --extra-index-url ${this.pypiPath}`;
+ },
+ pypiSetupCommand() {
+ return `[gitlab]
+repository = ${this.pypiSetupPath}
+username = __token__
+password = <your personal access token>`;
+ },
+ },
+ tracking: {
+ TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND,
+ TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND,
+ TRACKING_LABEL_CODE_INSTRUCTION,
+ },
+ 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}.',
+ ),
+ },
+ 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.tracking.TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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.tracking.TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND"
+ :tracking-label="$options.tracking.TRACKING_LABEL_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/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue
new file mode 100644
index 00000000000..d218a405af6
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue
@@ -0,0 +1,71 @@
+<script>
+import { GlLink, GlSprintf, GlTruncate } from '@gitlab/ui';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import PackageTags from '~/packages/shared/components/package_tags.vue';
+import PublishMethod from '~/packages/shared/components/publish_method.vue';
+import ListItem from '~/vue_shared/components/registry/list_item.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import { PACKAGE_DEFAULT_STATUS } from '../../constants';
+
+export default {
+ name: 'PackageListRow',
+ components: {
+ GlLink,
+ GlSprintf,
+ GlTruncate,
+ PackageTags,
+ PublishMethod,
+ ListItem,
+ TimeAgoTooltip,
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ packageLink() {
+ return `${getIdFromGraphQLId(this.packageEntity.id)}`;
+ },
+ disabledRow() {
+ return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS;
+ },
+ },
+};
+</script>
+
+<template>
+ <list-item :disabled="disabledRow">
+ <template #left-primary>
+ <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0">
+ <gl-link :href="packageLink" class="gl-text-body gl-min-w-0" :disabled="disabledRow">
+ <gl-truncate :text="packageEntity.name" />
+ </gl-link>
+
+ <package-tags
+ v-if="packageEntity.tags.nodes && packageEntity.tags.nodes.length"
+ class="gl-ml-3"
+ :tags="packageEntity.tags.nodes"
+ hide-label
+ :tag-display-limit="1"
+ />
+ </div>
+ </template>
+ <template #left-secondary>
+ {{ packageEntity.version }}
+ </template>
+
+ <template #right-primary>
+ <publish-method :package-entity="packageEntity" />
+ </template>
+
+ <template #right-secondary>
+ <gl-sprintf :message="__('Created %{timestamp}')">
+ <template #timestamp>
+ <time-ago-tooltip :time="packageEntity.createdAt" />
+ </template>
+ </gl-sprintf>
+ </template>
+ </list-item>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
new file mode 100644
index 00000000000..aad888b4433
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
@@ -0,0 +1,88 @@
+import { __, s__ } from '~/locale';
+
+export const PACKAGE_TYPE_CONAN = 'CONAN';
+export const PACKAGE_TYPE_MAVEN = 'MAVEN';
+export const PACKAGE_TYPE_NPM = 'NPM';
+export const PACKAGE_TYPE_NUGET = 'NUGET';
+export const PACKAGE_TYPE_PYPI = 'PYPI';
+export const PACKAGE_TYPE_COMPOSER = 'COMPOSER';
+export const PACKAGE_TYPE_RUBYGEMS = 'RUBYGEMS';
+export const PACKAGE_TYPE_GENERIC = 'GENERIC';
+export const PACKAGE_TYPE_DEBIAN = 'DEBIAN';
+export const PACKAGE_TYPE_HELM = 'HELM';
+
+export const DELETE_PACKAGE_TRACKING_ACTION = 'delete_package';
+export const REQUEST_DELETE_PACKAGE_TRACKING_ACTION = 'request_delete_package';
+export const CANCEL_DELETE_PACKAGE_TRACKING_ACTION = 'cancel_delete_package';
+export const PULL_PACKAGE_TRACKING_ACTION = 'pull_package';
+export const DELETE_PACKAGE_FILE_TRACKING_ACTION = 'delete_package_file';
+export const REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'request_delete_package_file';
+export const CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'cancel_delete_package_file';
+
+export const TRACKING_LABEL_CODE_INSTRUCTION = 'code_instruction';
+export const TRACKING_LABEL_CONAN_INSTALLATION = 'conan_installation';
+export const TRACKING_LABEL_MAVEN_INSTALLATION = 'maven_installation';
+export const TRACKING_LABEL_NPM_INSTALLATION = 'npm_installation';
+export const TRACKING_LABEL_NUGET_INSTALLATION = 'nuget_installation';
+export const TRACKING_LABEL_PYPI_INSTALLATION = 'pypi_installation';
+export const TRACKING_LABEL_COMPOSER_INSTALLATION = 'composer_installation';
+
+export const TRACKING_ACTION_INSTALLATION = 'installation';
+export const TRACKING_ACTION_REGISTRY_SETUP = 'registry_setup';
+
+export const TRACKING_ACTION_COPY_CONAN_COMMAND = 'copy_conan_command';
+export const TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND = 'copy_conan_setup_command';
+
+export const TRACKING_ACTION_COPY_MAVEN_XML = 'copy_maven_xml';
+export const TRACKING_ACTION_COPY_MAVEN_COMMAND = 'copy_maven_command';
+export const TRACKING_ACTION_COPY_MAVEN_SETUP = 'copy_maven_setup_xml';
+export const TRACKING_ACTION_COPY_GRADLE_INSTALL_COMMAND = 'copy_gradle_install_command';
+export const TRACKING_ACTION_COPY_GRADLE_ADD_TO_SOURCE_COMMAND =
+ 'copy_gradle_add_to_source_command';
+export const TRACKING_ACTION_COPY_KOTLIN_INSTALL_COMMAND = 'copy_kotlin_install_command';
+export const TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND =
+ 'copy_kotlin_add_to_source_command';
+
+export const TRACKING_ACTION_COPY_NPM_INSTALL_COMMAND = 'copy_npm_install_command';
+export const TRACKING_ACTION_COPY_NPM_SETUP_COMMAND = 'copy_npm_setup_command';
+export const TRACKING_ACTION_COPY_YARN_INSTALL_COMMAND = 'copy_yarn_install_command';
+export const TRACKING_ACTION_COPY_YARN_SETUP_COMMAND = 'copy_yarn_setup_command';
+
+export const TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND = 'copy_nuget_install_command';
+export const TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND = 'copy_nuget_setup_command';
+
+export const TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND = 'copy_pip_install_command';
+export const TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND = 'copy_pypi_setup_command';
+
+export const TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND =
+ 'copy_composer_registry_include_command';
+export const TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND =
+ 'copy_composer_package_include_command';
+
+export const TrackingCategories = {
+ [PACKAGE_TYPE_MAVEN]: 'MavenPackages',
+ [PACKAGE_TYPE_NPM]: 'NpmPackages',
+ [PACKAGE_TYPE_CONAN]: 'ConanPackages',
+};
+
+export const SHOW_DELETE_SUCCESS_ALERT = 'showSuccessDeleteAlert';
+export const DELETE_PACKAGE_ERROR_MESSAGE = s__(
+ 'PackageRegistry|Something went wrong while deleting the package.',
+);
+export const DELETE_PACKAGE_FILE_ERROR_MESSAGE = s__(
+ __('PackageRegistry|Something went wrong while deleting the package file.'),
+);
+export const DELETE_PACKAGE_FILE_SUCCESS_MESSAGE = s__(
+ 'PackageRegistry|Package file deleted successfully',
+);
+export const FETCH_PACKAGE_DETAILS_ERROR_MESSAGE = s__(
+ 'PackageRegistry|Failed to load the package data',
+);
+
+export const PACKAGE_ERROR_STATUS = 'ERROR';
+export const PACKAGE_DEFAULT_STATUS = 'DEFAULT';
+export const PACKAGE_HIDDEN_STATUS = 'HIDDEN';
+export const PACKAGE_PROCESSING_STATUS = 'PROCESSING';
+
+export const NPM_PACKAGE_MANAGER = 'npm';
+export const YARN_PACKAGE_MANAGER = 'yarn';
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragmentTypes.json b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragmentTypes.json
new file mode 100644
index 00000000000..c61a653d10b
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragmentTypes.json
@@ -0,0 +1,17 @@
+{
+ "__schema": {
+ "types": [
+ {
+ "kind": "UNION",
+ "name": "PackageMetadata",
+ "possibleTypes": [
+ { "name": "ComposerMetadata" },
+ { "name": "ConanMetadata" },
+ { "name": "MavenMetadata" },
+ { "name": "NugetMetadata" },
+ { "name": "PypiMetadata" }
+ ]
+ }
+ ]
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js b/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js
new file mode 100644
index 00000000000..f8cb5c516e2
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/index.js
@@ -0,0 +1,23 @@
+import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import introspectionQueryResultData from './fragmentTypes.json';
+
+const fragmentMatcher = new IntrospectionFragmentMatcher({
+ introspectionQueryResultData,
+});
+
+Vue.use(VueApollo);
+
+export const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(
+ {},
+ {
+ cacheConfig: {
+ fragmentMatcher,
+ },
+ assumeImmutableResults: true,
+ },
+ ),
+});
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql
new file mode 100644
index 00000000000..884980f24a9
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package.mutation.graphql
@@ -0,0 +1,5 @@
+mutation destroyPackage($id: PackagesPackageID!) {
+ destroyPackage(input: { id: $id }) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql
new file mode 100644
index 00000000000..f016640f57d
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/mutations/destroy_package_file.mutation.graphql
@@ -0,0 +1,5 @@
+mutation destroyPackageFile($id: PackagesPackageFileID!) {
+ destroyPackageFile(input: { id: $id }) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
new file mode 100644
index 00000000000..14aa14e9822
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
@@ -0,0 +1,111 @@
+query getPackageDetails($id: ID!) {
+ package(id: $id) {
+ id
+ name
+ packageType
+ version
+ createdAt
+ updatedAt
+ status
+ project {
+ path
+ }
+ tags(first: 10) {
+ nodes {
+ id
+ name
+ }
+ }
+ pipelines(first: 10) {
+ nodes {
+ ref
+ id
+ sha
+ createdAt
+ commitPath
+ path
+ user {
+ name
+ }
+ project {
+ name
+ webUrl
+ }
+ }
+ }
+ packageFiles(first: 100) {
+ nodes {
+ id
+ fileMd5
+ fileName
+ fileSha1
+ fileSha256
+ size
+ createdAt
+ downloadPath
+ }
+ }
+ versions(first: 100) {
+ nodes {
+ id
+ name
+ createdAt
+ version
+ status
+ tags(first: 1) {
+ nodes {
+ id
+ name
+ }
+ }
+ }
+ }
+ dependencyLinks {
+ nodes {
+ id
+ dependency {
+ id
+ name
+ versionPattern
+ }
+ dependencyType
+ metadata {
+ ... on NugetDependencyLinkMetadata {
+ id
+ targetFramework
+ }
+ }
+ }
+ }
+ metadata {
+ ... on ComposerMetadata {
+ targetSha
+ composerJson {
+ license
+ version
+ }
+ }
+ ... on PypiMetadata {
+ requiredPython
+ }
+ ... on ConanMetadata {
+ packageChannel
+ packageUsername
+ recipe
+ recipePath
+ }
+ ... on MavenMetadata {
+ appName
+ appGroup
+ appVersion
+ path
+ }
+
+ ... on NugetMetadata {
+ iconUrl
+ licenseUrl
+ projectUrl
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js
index 309b35a8084..d94bbd21035 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js
@@ -1,7 +1,8 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
+import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue';
+import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
import Translate from '~/vue_shared/translate';
-import PackagesApp from '../components/details/app.vue';
Vue.use(Translate);
@@ -14,9 +15,9 @@ export default () => {
const { canDelete, ...datasetOptions } = el.dataset;
return new Vue({
el,
+ apolloProvider,
provide: {
canDelete: parseBoolean(canDelete),
- titleComponent: 'PackageTitle',
...datasetOptions,
},
render(createElement) {
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/utils.js b/app/assets/javascripts/packages_and_registries/package_registry/utils.js
new file mode 100644
index 00000000000..ae886952c3e
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/utils.js
@@ -0,0 +1,40 @@
+import { s__ } from '~/locale';
+import {
+ PACKAGE_TYPE_CONAN,
+ PACKAGE_TYPE_MAVEN,
+ PACKAGE_TYPE_NPM,
+ PACKAGE_TYPE_NUGET,
+ PACKAGE_TYPE_PYPI,
+ PACKAGE_TYPE_COMPOSER,
+ PACKAGE_TYPE_RUBYGEMS,
+ PACKAGE_TYPE_GENERIC,
+ PACKAGE_TYPE_DEBIAN,
+ PACKAGE_TYPE_HELM,
+} from './constants';
+
+export const getPackageTypeLabel = (packageType) => {
+ switch (packageType) {
+ case PACKAGE_TYPE_CONAN:
+ return s__('PackageRegistry|Conan');
+ case PACKAGE_TYPE_MAVEN:
+ return s__('PackageRegistry|Maven');
+ case PACKAGE_TYPE_NPM:
+ return s__('PackageRegistry|npm');
+ case PACKAGE_TYPE_NUGET:
+ return s__('PackageRegistry|NuGet');
+ case PACKAGE_TYPE_PYPI:
+ return s__('PackageRegistry|PyPI');
+ case PACKAGE_TYPE_RUBYGEMS:
+ return s__('PackageRegistry|RubyGems');
+ case PACKAGE_TYPE_COMPOSER:
+ return s__('PackageRegistry|Composer');
+ case PACKAGE_TYPE_GENERIC:
+ return s__('PackageRegistry|Generic');
+ case PACKAGE_TYPE_DEBIAN:
+ return s__('PackageRegistry|Debian');
+ case PACKAGE_TYPE_HELM:
+ return s__('PackageRegistry|Helm');
+ default:
+ return null;
+ }
+};