adjustments/improvements to pinned versions

This commit is contained in:
Jake Potrebic 2022-06-21 10:21:27 -07:00
parent 1ec32bf8dd
commit f10552bb75
No known key found for this signature in database
GPG Key ID: 27CC63F7CBC866C7
12 changed files with 107 additions and 51 deletions

View File

@ -481,13 +481,14 @@
"setRecommendedTooltip": "Set this version as recommended for {0} platform",
"pinned": {
"tooltip": {
"false": "Pin this version to the main project page",
"true": "Remove this pinned version from the main project page",
"none": "Pin this version to the main project page",
"version": "Remove this pinned version from the main project page",
"channel": "This version is pinned via its channel"
},
"button": {
"false": "Pin",
"true": "Unpin"
"none": "Pin",
"version": "Unpin",
"channel": "Unpin"
},
"request": {
"true": "Successfully pinned this version",

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { NamedPermission, Platform, ReviewState, Visibility, ChannelFlag } from "~/types/enums";
import { NamedPermission, Platform, ReviewState, Visibility, ChannelFlag, PinnedStatus } from "~/types/enums";
import { HangarProject, HangarVersion, IPlatform } from "hangar-internal";
import { computed, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
@ -237,19 +237,17 @@ async function restoreVersion() {
</Tooltip>
<Tooltip>
<template #content>
<span v-if="!projectVersion.pinned && projectVersion.channel.flags.indexOf(ChannelFlag.PINNED) > -1">{{
i18n.t("version.page.pinned.tooltip.channel")
}}</span>
<span v-else>{{ i18n.t(`version.page.pinned.tooltip.${projectVersion.pinned}`) }}</span>
<span v-if="projectVersion.pinnedStatus === PinnedStatus.CHANNEL">{{ i18n.t("version.page.pinned.tooltip.channel") }}</span>
<span v-else>{{ i18n.t(`version.page.pinned.tooltip.${projectVersion.pinnedStatus.toLowerCase()}`) }}</span>
</template>
<Button
size="small"
:disabled="!projectVersion.pinned && projectVersion.channel.flags.indexOf(ChannelFlag.PINNED) > -1"
@click="setPinned(!projectVersion.pinned)"
:disabled="projectVersion.pinnedStatus === PinnedStatus.CHANNEL"
@click="setPinned(projectVersion.pinnedStatus === PinnedStatus.NONE)"
>
<IconMdiPinOff v-if="projectVersion.pinned" class="mr-1" />
<IconMdiPinOff v-if="projectVersion.pinnedStatus !== PinnedStatus.NONE" class="mr-1" />
<IconMdiPin v-else class="mr-1" />
{{ i18n.t(`version.page.pinned.button.${projectVersion.pinned}`) }}
{{ i18n.t(`version.page.pinned.button.${projectVersion.pinnedStatus.toLowerCase()}`) }}
</Button>
</Tooltip>

View File

@ -1,4 +1,4 @@
import { ChannelFlag } from "~/types/enums";
import { ChannelFlag, PinnedStatus } from "~/types/enums";
declare module "hangar-api" {
import type { Model, Named, ProjectNamespace, TagColor, Visible } from "hangar-api";
@ -49,6 +49,7 @@ declare module "hangar-api" {
tags: Tag[];
channel: ProjectChannel;
pinned: boolean;
pinnedStatus: PinnedStatus;
recommended: Platform[];
}

View File

@ -112,3 +112,9 @@ export enum ChannelFlag {
SKIP_REVIEW_QUEUE = "SKIP_REVIEW_QUEUE",
PINNED = "PINNED",
}
export enum PinnedStatus {
CHANNEL = "CHANNEL",
RELEASE = "RELEASE",
NONE = "NONE",
}

View File

@ -5,7 +5,6 @@ import '@vue/runtime-core'
declare module '@vue/runtime-core' {
export interface GlobalComponents {
IconMdiAccountArrowRight: typeof import('~icons/mdi/account-arrow-right')['default']
IconMdiAccountPlus: typeof import('~icons/mdi/account-plus')['default']
IconMdiAlert: typeof import('~icons/mdi/alert')['default']
IconMdiAlertBox: typeof import('~icons/mdi/alert-box')['default']
@ -15,18 +14,12 @@ declare module '@vue/runtime-core' {
IconMdiCalendar: typeof import('~icons/mdi/calendar')['default']
IconMdiCheck: typeof import('~icons/mdi/check')['default']
IconMdiCheckBold: typeof import('~icons/mdi/check-bold')['default']
IconMdiCheckboxBlankCircleOutline: typeof import('~icons/mdi/checkbox-blank-circle-outline')['default']
IconMdiCheckboxMarkedCircle: typeof import('~icons/mdi/checkbox-marked-circle')['default']
IconMdiCheckCircle: typeof import('~icons/mdi/check-circle')['default']
IconMdiCheckCircleOutline: typeof import('~icons/mdi/check-circle-outline')['default']
IconMdiCheckDecagram: typeof import('~icons/mdi/check-decagram')['default']
IconMdiCheckDecagramOutline: typeof import('~icons/mdi/check-decagram-outline')['default']
IconMdiChevronDoubleDown: typeof import('~icons/mdi/chevron-double-down')['default']
IconMdiCircle: typeof import('~icons/mdi/circle')['default']
IconMdiClipboardOutline: typeof import('~icons/mdi/clipboard-outline')['default']
IconMdiClose: typeof import('~icons/mdi/close')['default']
IconMdiCloseCircle: typeof import('~icons/mdi/close-circle')['default']
IconMdiCloudSearch: typeof import('~icons/mdi/cloud-search')['default']
IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
IconMdiContentSave: typeof import('~icons/mdi/content-save')['default']
IconMdiDelete: typeof import('~icons/mdi/delete')['default']
@ -37,17 +30,11 @@ declare module '@vue/runtime-core' {
IconMdiEye: typeof import('~icons/mdi/eye')['default']
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
IconMdiFile: typeof import('~icons/mdi/file')['default']
IconMdiFileFind: typeof import('~icons/mdi/file-find')['default']
IconMdiFlag: typeof import('~icons/mdi/flag')['default']
IconMdiFormatListNumbered: typeof import('~icons/mdi/format-list-numbered')['default']
IconMdiHome: typeof import('~icons/mdi/home')['default']
IconMdiInformation: typeof import('~icons/mdi/information')['default']
IconMdiKeyOutline: typeof import('~icons/mdi/key-outline')['default']
IconMdiLicense: typeof import('~icons/mdi/license')['default']
IconMdiLink: typeof import('~icons/mdi/link')['default']
IconMdiListStatus: typeof import('~icons/mdi/list-status')['default']
IconMdiLockOpenOutline: typeof import('~icons/mdi/lock-open-outline')['default']
IconMdiLockOutline: typeof import('~icons/mdi/lock-outline')['default']
IconMdiMenu: typeof import('~icons/mdi/menu')['default']
IconMdiMenuDown: typeof import('~icons/mdi/menu-down')['default']
IconMdiOpenInNew: typeof import('~icons/mdi/open-in-new')['default']
@ -56,20 +43,10 @@ declare module '@vue/runtime-core' {
IconMdiPinOff: typeof import('~icons/mdi/pin-off')['default']
IconMdiPlay: typeof import('~icons/mdi/play')['default']
IconMdiPlus: typeof import('~icons/mdi/plus')['default']
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
IconMdiRenameBox: typeof import('~icons/mdi/rename-box')['default']
IconMdiSend: typeof import('~icons/mdi/send')['default']
IconMdiSort: typeof import('~icons/mdi/sort')['default']
IconMdiSortAscending: typeof import('~icons/mdi/sort-ascending')['default']
IconMdiSortDescending: typeof import('~icons/mdi/sort-descending')['default']
IconMdiSortVariant: typeof import('~icons/mdi/sort-variant')['default']
IconMdiStar: typeof import('~icons/mdi/star')['default']
IconMdiStarOutline: typeof import('~icons/mdi/star-outline')['default']
IconMdiStop: typeof import('~icons/mdi/stop')['default']
IconMdiTag: typeof import('~icons/mdi/tag')['default']
IconMdiTrophy: typeof import('~icons/mdi/trophy')['default']
IconMdiUndo: typeof import('~icons/mdi/undo')['default']
IconMdiUpload: typeof import('~icons/mdi/upload')['default']
IconMdiWeatherNight: typeof import('~icons/mdi/weather-night')['default']
IconMdiWhiteBalanceSunny: typeof import('~icons/mdi/white-balance-sunny')['default']
RouterLink: typeof import('vue-router')['RouterLink']

View File

@ -35,7 +35,11 @@ public interface HangarVersionsDAO {
" pc.name pc_name," +
" pc.color pc_color," +
" pc.flags pc_flags," +
" exists(SELECT ppv.id FROM pinned_project_versions ppv WHERE ppv.version_id = pv.id) as pinned," +
" CASE" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'channel') THEN 'CHANNEL'" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'version') THEN 'VERSION'" +
" ELSE 'NONE'" +
" END AS pinnedStatus," +
" array(SELECT DISTINCT rpv.platform FROM recommended_project_versions rpv WHERE rpv.version_id = pv.id ORDER BY rpv.platform) as recommended," +
" ru.name approved_by" +
" FROM project_versions pv" +
@ -73,7 +77,11 @@ public interface HangarVersionsDAO {
" pc.name pc_name," +
" pc.color pc_color," +
" pc.flags pc_flags," +
" exists(SELECT ppv.id FROM pinned_project_versions ppv WHERE ppv.version_id = pv.id) as pinned," +
" CASE" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'channel') THEN 'CHANNEL'" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'version') THEN 'VERSION'" +
" ELSE 'NONE'" +
" END AS pinnedStatus," +
" array(SELECT DISTINCT rpv.platform FROM recommended_project_versions rpv WHERE rpv.version_id = pv.id ORDER BY rpv.platform) as recommended," +
" ru.name approved_by" +
" FROM project_versions pv" +

View File

@ -50,7 +50,11 @@ public interface VersionsApiDAO {
" pc.name pc_name," +
" pc.color pc_color," +
" pc.flags pc_flags," +
" exists(SELECT ppv.id FROM pinned_project_versions ppv WHERE ppv.version_id = pv.id) as pinned," +
" CASE" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'channel') THEN 'CHANNEL'" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'version') THEN 'VERSION'" +
" ELSE 'NONE'" +
" END AS pinnedStatus," +
" array(SELECT DISTINCT rpv.platform FROM recommended_project_versions rpv WHERE rpv.version_id = pv.id ORDER BY rpv.platform) as recommended" +
" FROM project_versions pv" +
" JOIN project_channels pc ON pv.channel_id = pc.id" +
@ -86,7 +90,11 @@ public interface VersionsApiDAO {
" pc.name pc_name," +
" pc.color pc_color," +
" pc.flags pc_flags," +
" exists(SELECT ppv.id FROM pinned_project_versions ppv WHERE ppv.version_id = pv.id) as pinned," +
" CASE" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'channel') THEN 'CHANNEL'" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'version') THEN 'VERSION'" +
" ELSE 'NONE'" +
" END AS pinnedStatus," +
" array(SELECT DISTINCT rpv.platform FROM recommended_project_versions rpv WHERE rpv.version_id = pv.id ORDER BY rpv.platform) as recommended" +
" FROM project_versions pv" +
" JOIN project_channels pc ON pv.channel_id = pc.id" +
@ -125,7 +133,11 @@ public interface VersionsApiDAO {
" pc.name pc_name," +
" pc.color pc_color," +
" pc.flags pc_flags," +
" exists(SELECT ppv.id FROM pinned_project_versions ppv WHERE ppv.version_id = pv.id) as pinned," +
" CASE" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'channel') THEN 'CHANNEL'" +
" WHEN exists(SELECT * FROM pinned_versions piv WHERE piv.version_id = pv.id AND lower(type) = 'version') THEN 'VERSION'" +
" ELSE 'NONE'" +
" END AS pinnedStatus," +
" array(SELECT DISTINCT rpv.platform FROM recommended_project_versions rpv WHERE rpv.version_id = pv.id ORDER BY rpv.platform) as recommended" +
" FROM project_versions pv" +
" JOIN projects p ON pv.project_id = p.id" +

View File

@ -18,8 +18,8 @@ public class Version extends VersionCompact {
private final Map<Platform, Set<PluginDependency>> pluginDependencies;
private final Map<Platform, Set<String>> platformDependencies;
public Version(final OffsetDateTime createdAt, @ColumnName("version_string") final String name, final Visibility visibility, final String description, @Nested("vs") final VersionStats stats, @Nested("fi") final FileInfo fileInfo, final String externalUrl, final String author, @EnumByOrdinal final ReviewState reviewState, @Nested("pc") final ProjectChannel channel, final boolean pinned, final List<Platform> recommended, final Long postId) {
super(createdAt, name, visibility, description, stats, fileInfo, externalUrl, author, reviewState, channel, pinned, recommended, postId);
public Version(final OffsetDateTime createdAt, @ColumnName("version_string") final String name, final Visibility visibility, final String description, @Nested("vs") final VersionStats stats, @Nested("fi") final FileInfo fileInfo, final String externalUrl, final String author, @EnumByOrdinal final ReviewState reviewState, @Nested("pc") final ProjectChannel channel, final PinnedStatus pinnedStatus, final List<Platform> recommended, final Long postId) {
super(createdAt, name, visibility, description, stats, fileInfo, externalUrl, author, reviewState, channel, pinnedStatus, recommended, postId);
this.pluginDependencies = new EnumMap<>(Platform.class);
this.platformDependencies = new EnumMap<>(Platform.class);
}

View File

@ -1,5 +1,6 @@
package io.papermc.hangar.model.api.project.version;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.papermc.hangar.model.Model;
import io.papermc.hangar.model.Named;
import io.papermc.hangar.model.Visible;
@ -11,6 +12,7 @@ import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jdbi.v3.core.enums.EnumByName;
import org.jdbi.v3.core.enums.EnumByOrdinal;
import org.jdbi.v3.core.mapper.Nested;
import org.jdbi.v3.core.mapper.reflect.ColumnName;
@ -27,11 +29,11 @@ public class VersionCompact extends Model implements Named, Visible {
private final ReviewState reviewState;
private final Set<Tag> tags;
private final ProjectChannel channel;
private final boolean pinned;
private final PinnedStatus pinnedStatus;
private final List<Platform> recommended;
private final Long postId;
protected VersionCompact(final OffsetDateTime createdAt, @ColumnName("version_string") final String name, final Visibility visibility, final String description, @Nested("vs") final VersionStats stats, @Nested("fi") final FileInfo fileInfo, final String externalUrl, final String author, @EnumByOrdinal final ReviewState reviewState, @Nested("pc") final ProjectChannel channel, final boolean pinned, final List<Platform> recommended, final Long postId) {
protected VersionCompact(final OffsetDateTime createdAt, @ColumnName("version_string") final String name, final Visibility visibility, final String description, @Nested("vs") final VersionStats stats, @Nested("fi") final FileInfo fileInfo, final String externalUrl, final String author, @EnumByOrdinal final ReviewState reviewState, @Nested("pc") final ProjectChannel channel, final PinnedStatus pinnedStatus, final List<Platform> recommended, final Long postId) {
super(createdAt);
this.name = name;
this.visibility = visibility;
@ -42,7 +44,7 @@ public class VersionCompact extends Model implements Named, Visible {
this.author = author;
this.reviewState = reviewState;
this.channel = channel;
this.pinned = pinned;
this.pinnedStatus = pinnedStatus;
this.tags = new HashSet<>();
this.recommended = recommended;
this.postId = postId;
@ -90,8 +92,8 @@ public class VersionCompact extends Model implements Named, Visible {
return this.channel;
}
public boolean isPinned() {
return this.pinned;
public PinnedStatus getPinnedStatus() {
return this.pinnedStatus;
}
public List<Platform> getRecommended() {
@ -101,4 +103,12 @@ public class VersionCompact extends Model implements Named, Visible {
public Long getPostId() {
return this.postId;
}
@EnumByName
@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum PinnedStatus {
NONE,
VERSION,
CHANNEL
}
}

View File

@ -21,8 +21,8 @@ public class HangarVersion extends Version implements Identified {
private final long id;
private final String approvedBy;
public HangarVersion(final OffsetDateTime createdAt, @ColumnName("version_string") final String name, final Visibility visibility, final String description, @Nested("vs") final VersionStats stats, @Nested("fi") final FileInfo fileInfo, final String externalUrl, final String author, @EnumByOrdinal final ReviewState reviewState, @Nested("pc") final ProjectChannel channel, final boolean pinned, final List<Platform> recommended, final long id, final String approvedBy, final long postId) {
super(createdAt, name, visibility, description, stats, fileInfo, externalUrl, author, reviewState, channel, pinned, recommended, postId);
public HangarVersion(final OffsetDateTime createdAt, @ColumnName("version_string") final String name, final Visibility visibility, final String description, @Nested("vs") final VersionStats stats, @Nested("fi") final FileInfo fileInfo, final String externalUrl, final String author, @EnumByOrdinal final ReviewState reviewState, @Nested("pc") final ProjectChannel channel, final PinnedStatus pinnedStatus, final List<Platform> recommended, final long id, final String approvedBy, final long postId) {
super(createdAt, name, visibility, description, stats, fileInfo, externalUrl, author, reviewState, channel, pinnedStatus, recommended, postId);
this.id = id;
this.approvedBy = approvedBy;
}

View File

@ -24,6 +24,7 @@ public class PinnedVersionService extends HangarComponent {
}
public void addPinnedVersion(final long projectId, final long versionId) {
// TODO check if already pinned via channel
this.pinnedProjectVersionsDAO.insert(new PinnedProjectVersionTable(projectId, versionId));
}

View File

@ -0,0 +1,42 @@
CREATE OR REPLACE VIEW pinned_versions AS
SELECT * FROM (
SELECT DISTINCT ON (version_id) version_id,
id,
created_at,
type,
version_string,
platforms,
project_id
FROM (
SELECT ppv.id,
ppv.version_id,
pv.created_at,
pv.version_string,
array(SELECT DISTINCT pv.platform FROM project_version_platform_dependencies pvpd
JOIN platform_versions pv ON pv.id = pvpd.platform_version_id
WHERE pvpd.platform_version_id = pv.id
ORDER BY pv.platform
) AS platforms,
'version' AS type,
pv.project_id
FROM pinned_project_versions ppv
JOIN project_versions pv ON pv.id = ppv.version_id
UNION ALL
(SELECT pc.id,
pv.id AS version_id,
pv.created_at,
pv.version_string,
array(SELECT DISTINCT pv.platform FROM project_version_platform_dependencies pvpd
JOIN platform_versions pv ON pv.id = pvpd.platform_version_id
WHERE pvpd.platform_version_id = pv.id
ORDER BY pv.platform
) AS platforms,
'channel' as type,
pv.project_id
FROM project_channels pc
JOIN project_versions pv ON pc.id = pv.channel_id
WHERE 3 = ANY(pc.flags)
ORDER BY pv.created_at DESC
LIMIT 1)
) AS pvs
) AS t ORDER BY t.created_at DESC;