summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/packages/shared
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/packages/shared')
-rw-r--r--app/assets/javascripts/packages/shared/components/package_list_row.vue139
-rw-r--r--app/assets/javascripts/packages/shared/components/package_tags.vue108
-rw-r--r--app/assets/javascripts/packages/shared/components/packages_list_loader.vue86
-rw-r--r--app/assets/javascripts/packages/shared/components/publish_method.vue61
-rw-r--r--app/assets/javascripts/packages/shared/constants.js24
-rw-r--r--app/assets/javascripts/packages/shared/utils.js36
6 files changed, 454 insertions, 0 deletions
diff --git a/app/assets/javascripts/packages/shared/components/package_list_row.vue b/app/assets/javascripts/packages/shared/components/package_list_row.vue
new file mode 100644
index 00000000000..e000279b794
--- /dev/null
+++ b/app/assets/javascripts/packages/shared/components/package_list_row.vue
@@ -0,0 +1,139 @@
+<script>
+import { GlButton, GlIcon, GlLink, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import PackageTags from './package_tags.vue';
+import PublishMethod from './publish_method.vue';
+import { getPackageTypeLabel } from '../utils';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+
+export default {
+ name: 'PackageListRow',
+ components: {
+ GlButton,
+ GlIcon,
+ GlLink,
+ GlSprintf,
+ PackageTags,
+ PublishMethod,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ packageLink: {
+ type: String,
+ required: true,
+ },
+ disableDelete: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ isGroup: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ showPackageType: {
+ type: Boolean,
+ default: true,
+ required: false,
+ },
+ },
+ computed: {
+ packageType() {
+ return getPackageTypeLabel(this.packageEntity.package_type);
+ },
+ hasPipeline() {
+ return Boolean(this.packageEntity.pipeline);
+ },
+ hasProjectLink() {
+ return Boolean(this.packageEntity.project_path);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-responsive-table-row" data-qa-selector="packages-row">
+ <div class="table-section section-50 d-flex flex-md-column justify-content-between flex-wrap">
+ <div class="d-flex align-items-center mr-2">
+ <gl-link
+ :href="packageLink"
+ data-qa-selector="package_link"
+ class="text-dark font-weight-bold mb-md-1"
+ >
+ {{ packageEntity.name }}
+ </gl-link>
+
+ <package-tags
+ v-if="packageEntity.tags && packageEntity.tags.length"
+ class="gl-ml-3"
+ :tags="packageEntity.tags"
+ hide-label
+ :tag-display-limit="1"
+ />
+ </div>
+
+ <div class="d-flex text-secondary text-truncate mt-md-2">
+ <span>{{ packageEntity.version }}</span>
+
+ <div v-if="hasPipeline" class="d-none d-md-inline-block ml-1">
+ <gl-sprintf :message="s__('PackageRegistry|published by %{author}')">
+ <template #author>{{ packageEntity.pipeline.user.name }}</template>
+ </gl-sprintf>
+ </div>
+
+ <div v-if="hasProjectLink" class="d-flex align-items-center">
+ <gl-icon name="review-list" class="text-secondary ml-2 mr-1" />
+
+ <gl-link
+ data-testid="packages-row-project"
+ :href="`/${packageEntity.project_path}`"
+ class="text-secondary"
+ >{{ packageEntity.projectPathName }}</gl-link
+ >
+ </div>
+
+ <div v-if="showPackageType" class="d-flex align-items-center" data-testid="package-type">
+ <gl-icon name="package" class="text-secondary ml-2 mr-1" />
+ <span>{{ packageType }}</span>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="table-section d-flex flex-md-column justify-content-between align-items-md-end"
+ :class="disableDelete ? 'section-50' : 'section-40'"
+ >
+ <publish-method :package-entity="packageEntity" :is-group="isGroup" />
+
+ <div class="text-secondary order-0 order-md-1 mt-md-2">
+ <gl-sprintf :message="__('Created %{timestamp}')">
+ <template #timestamp>
+ <span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
+ {{ timeFormatted(packageEntity.created_at) }}
+ </span>
+ </template>
+ </gl-sprintf>
+ </div>
+ </div>
+
+ <div v-if="!disableDelete" class="table-section section-10 d-flex justify-content-end">
+ <gl-button
+ data-testid="action-delete"
+ icon="remove"
+ category="primary"
+ variant="danger"
+ :title="s__('PackageRegistry|Remove package')"
+ :aria-label="s__('PackageRegistry|Remove package')"
+ :disabled="!packageEntity._links.delete_api_path"
+ @click="$emit('packageToDelete', packageEntity)"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/shared/components/package_tags.vue b/app/assets/javascripts/packages/shared/components/package_tags.vue
new file mode 100644
index 00000000000..391f53c225b
--- /dev/null
+++ b/app/assets/javascripts/packages/shared/components/package_tags.vue
@@ -0,0 +1,108 @@
+<script>
+import { GlBadge, GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import { n__ } from '~/locale';
+
+export default {
+ name: 'PackageTags',
+ components: {
+ GlBadge,
+ GlIcon,
+ GlSprintf,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ tagDisplayLimit: {
+ type: Number,
+ required: false,
+ default: 2,
+ },
+ tags: {
+ type: Array,
+ required: true,
+ default: () => [],
+ },
+ hideLabel: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ tagCount() {
+ return this.tags.length;
+ },
+ tagsToRender() {
+ return this.tags.slice(0, this.tagDisplayLimit);
+ },
+ moreTagsDisplay() {
+ return Math.max(0, this.tags.length - this.tagDisplayLimit);
+ },
+ moreTagsTooltip() {
+ if (this.moreTagsDisplay) {
+ return this.tags
+ .slice(this.tagDisplayLimit)
+ .map(x => x.name)
+ .join(', ');
+ }
+
+ return '';
+ },
+ tagsDisplay() {
+ return n__('%d tag', '%d tags', this.tagCount);
+ },
+ },
+ methods: {
+ tagBadgeClass(index) {
+ return {
+ 'gl-display-none': true,
+ 'gl-display-flex': this.tagCount === 1,
+ 'd-md-flex': this.tagCount > 1,
+ 'gl-mr-2': index !== this.tagsToRender.length - 1,
+ 'gl-ml-3': !this.hideLabel && index === 0,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-align-items-center">
+ <div v-if="!hideLabel" data-testid="tagLabel" class="gl-display-flex gl-align-items-center">
+ <gl-icon name="labels" class="gl-text-gray-500 gl-mr-3" />
+ <span class="gl-font-weight-bold">{{ tagsDisplay }}</span>
+ </div>
+
+ <gl-badge
+ v-for="(tag, index) in tagsToRender"
+ :key="index"
+ data-testid="tagBadge"
+ :class="tagBadgeClass(index)"
+ variant="info"
+ >{{ tag.name }}</gl-badge
+ >
+
+ <gl-badge
+ v-if="moreTagsDisplay"
+ v-gl-tooltip
+ data-testid="moreBadge"
+ variant="muted"
+ :title="moreTagsTooltip"
+ class="gl-display-none d-md-flex gl-ml-2"
+ ><gl-sprintf :message="__('+%{tags} more')">
+ <template #tags>
+ {{ moreTagsDisplay }}
+ </template>
+ </gl-sprintf></gl-badge
+ >
+
+ <gl-badge
+ v-if="moreTagsDisplay && hideLabel"
+ data-testid="moreBadge"
+ variant="muted"
+ class="d-md-none gl-ml-2"
+ >{{ tagsDisplay }}</gl-badge
+ >
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/shared/components/packages_list_loader.vue b/app/assets/javascripts/packages/shared/components/packages_list_loader.vue
new file mode 100644
index 00000000000..cd9ef74d467
--- /dev/null
+++ b/app/assets/javascripts/packages/shared/components/packages_list_loader.vue
@@ -0,0 +1,86 @@
+<script>
+import { GlSkeletonLoader } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlSkeletonLoader,
+ },
+ props: {
+ isGroup: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ desktopShapes() {
+ return this.isGroup ? this.$options.shapes.groups : this.$options.shapes.projects;
+ },
+ desktopHeight() {
+ return this.isGroup ? 38 : 54;
+ },
+ mobileHeight() {
+ return this.isGroup ? 160 : 170;
+ },
+ },
+ shapes: {
+ groups: [
+ { type: 'rect', width: '100', height: '10', x: '0', y: '15' },
+ { type: 'rect', width: '100', height: '10', x: '195', y: '15' },
+ { type: 'rect', width: '60', height: '10', x: '475', y: '15' },
+ { type: 'rect', width: '60', height: '10', x: '675', y: '15' },
+ { type: 'rect', width: '100', height: '10', x: '900', y: '15' },
+ ],
+ projects: [
+ { type: 'rect', width: '220', height: '10', x: '0', y: '20' },
+ { type: 'rect', width: '60', height: '10', x: '305', y: '20' },
+ { type: 'rect', width: '60', height: '10', x: '535', y: '20' },
+ { type: 'rect', width: '100', height: '10', x: '760', y: '20' },
+ { type: 'rect', width: '30', height: '30', x: '970', y: '10', ref: 'button-loader' },
+ ],
+ },
+ rowsToRender: {
+ mobile: 5,
+ desktop: 20,
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="d-xs-flex flex-column d-md-none">
+ <gl-skeleton-loader
+ v-for="index in $options.rowsToRender.mobile"
+ :key="index"
+ :width="500"
+ :height="mobileHeight"
+ preserve-aspect-ratio="xMinYMax meet"
+ >
+ <rect width="500" height="10" x="0" y="15" rx="4" />
+ <rect width="500" height="10" x="0" y="45" rx="4" />
+ <rect width="500" height="10" x="0" y="75" rx="4" />
+ <rect width="500" height="10" x="0" y="105" rx="4" />
+ <rect v-if="isGroup" width="500" height="10" x="0" y="135" rx="4" />
+ <rect v-else width="30" height="30" x="470" y="135" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+
+ <div class="d-none d-md-flex flex-column">
+ <gl-skeleton-loader
+ v-for="index in $options.rowsToRender.desktop"
+ :key="index"
+ :width="1000"
+ :height="desktopHeight"
+ preserve-aspect-ratio="xMinYMax meet"
+ >
+ <component
+ :is="r.type"
+ v-for="(r, rIndex) in desktopShapes"
+ :key="rIndex"
+ rx="4"
+ v-bind="r"
+ />
+ </gl-skeleton-loader>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/shared/components/publish_method.vue b/app/assets/javascripts/packages/shared/components/publish_method.vue
new file mode 100644
index 00000000000..1e18562a421
--- /dev/null
+++ b/app/assets/javascripts/packages/shared/components/publish_method.vue
@@ -0,0 +1,61 @@
+<script>
+import { GlIcon, GlLink } from '@gitlab/ui';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { getCommitLink } from '../utils';
+
+export default {
+ name: 'PublishMethod',
+ components: {
+ ClipboardButton,
+ GlIcon,
+ GlLink,
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ isGroup: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ hasPipeline() {
+ return Boolean(this.packageEntity.pipeline);
+ },
+ packageShaShort() {
+ return this.packageEntity.pipeline?.sha.substring(0, 8);
+ },
+ linkToCommit() {
+ return getCommitLink(this.packageEntity, this.isGroup);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="d-flex align-items-center text-secondary order-1 order-md-0 mb-md-1">
+ <template v-if="hasPipeline">
+ <gl-icon name="git-merge" class="mr-1" />
+ <strong ref="pipeline-ref" class="mr-1 text-dark">{{ packageEntity.pipeline.ref }}</strong>
+
+ <gl-icon name="commit" class="mr-1" />
+ <gl-link ref="pipeline-sha" :href="linkToCommit" class="mr-1">{{ packageShaShort }}</gl-link>
+
+ <clipboard-button
+ :text="packageEntity.pipeline.sha"
+ :title="__('Copy commit SHA')"
+ css-class="border-0 text-secondary py-0 px-1"
+ />
+ </template>
+
+ <template v-else>
+ <gl-icon name="upload" class="mr-1" />
+ <strong ref="manual-ref" class="text-dark">{{
+ s__('PackageRegistry|Manually Published')
+ }}</strong>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/shared/constants.js b/app/assets/javascripts/packages/shared/constants.js
new file mode 100644
index 00000000000..279c2959fa9
--- /dev/null
+++ b/app/assets/javascripts/packages/shared/constants.js
@@ -0,0 +1,24 @@
+export const PackageType = {
+ CONAN: 'conan',
+ MAVEN: 'maven',
+ NPM: 'npm',
+ NUGET: 'nuget',
+ PYPI: 'pypi',
+ COMPOSER: 'composer',
+};
+
+export const TrackingActions = {
+ DELETE_PACKAGE: 'delete_package',
+ REQUEST_DELETE_PACKAGE: 'request_delete_package',
+ CANCEL_DELETE_PACKAGE: 'cancel_delete_package',
+ PULL_PACKAGE: 'pull_package',
+ COMING_SOON_REQUESTED: 'activate_coming_soon_requested',
+ COMING_SOON_LIST: 'click_coming_soon_issue_link',
+ COMING_SOON_HELP: 'click_coming_soon_documentation_link',
+};
+
+export const TrackingCategories = {
+ [PackageType.MAVEN]: 'MavenPackages',
+ [PackageType.NPM]: 'NpmPackages',
+ [PackageType.CONAN]: 'ConanPackages',
+};
diff --git a/app/assets/javascripts/packages/shared/utils.js b/app/assets/javascripts/packages/shared/utils.js
new file mode 100644
index 00000000000..a0c7389651d
--- /dev/null
+++ b/app/assets/javascripts/packages/shared/utils.js
@@ -0,0 +1,36 @@
+import { s__ } from '~/locale';
+import { PackageType, TrackingCategories } from './constants';
+
+export const packageTypeToTrackCategory = type =>
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ `UI::${TrackingCategories[type]}`;
+
+export const beautifyPath = path => (path ? path.split('/').join(' / ') : '');
+
+export const getPackageTypeLabel = packageType => {
+ switch (packageType) {
+ case PackageType.CONAN:
+ return s__('PackageType|Conan');
+ case PackageType.MAVEN:
+ return s__('PackageType|Maven');
+ case PackageType.NPM:
+ return s__('PackageType|NPM');
+ case PackageType.NUGET:
+ return s__('PackageType|NuGet');
+ case PackageType.PYPI:
+ return s__('PackageType|PyPi');
+ case PackageType.COMPOSER:
+ return s__('PackageType|Composer');
+
+ default:
+ return null;
+ }
+};
+
+export const getCommitLink = ({ project_path: projectPath, pipeline = {} }, isGroup = false) => {
+ if (isGroup) {
+ return `/${projectPath}/commit/${pipeline.sha}`;
+ }
+
+ return `../commit/${pipeline.sha}`;
+};