feat(releases-widget): limit number of displayed releases and custom name for repositories (#2974)

Co-authored-by: Andre Silva <asilva01@acuitysso.com>
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
Andre Silva
2025-05-10 21:06:14 +01:00
committed by GitHub
parent b8bdf1836a
commit 8c1b365733
5 changed files with 70 additions and 14 deletions

View File

@@ -2072,6 +2072,10 @@
"showDetails": {
"label": "Show Details"
},
"topReleases": {
"label": "Top Releases",
"description": "The max number of latest releases to show. Zero means no limit."
},
"repositories": {
"label": "Repositories",
"addRRepository": {
@@ -2084,6 +2088,9 @@
"label": "Identifier",
"placeholder": "Name or Owner/Name"
},
"name": {
"label": "Name"
},
"versionFilter": {
"label": "Version Filter",
"prefix": {

View File

@@ -40,6 +40,7 @@ export const WidgetMultiReleasesRepositoriesInput = ({
(repository: ReleasesRepository, index: number): FormValidation => {
form.setFieldValue(`options.${property}.${index}.providerKey`, repository.providerKey);
form.setFieldValue(`options.${property}.${index}.identifier`, repository.identifier);
form.setFieldValue(`options.${property}.${index}.name`, repository.name);
form.setFieldValue(`options.${property}.${index}.versionFilter`, repository.versionFilter);
form.setFieldValue(`options.${property}.${index}.iconUrl`, repository.iconUrl);
@@ -123,7 +124,8 @@ export const WidgetMultiReleasesRepositoriesInput = ({
<Group justify="space-between" align="center" style={{ flex: 1 }} gap={5}>
<Text size="sm" style={{ flex: 1, whiteSpace: "nowrap" }}>
{repository.identifier}
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
{repository.name || repository.identifier}
</Text>
<Text c="dimmed" size="xs" ta="end" style={{ flex: 1, whiteSpace: "nowrap" }}>
@@ -178,6 +180,11 @@ const formatVersionFilterRegex = (versionFilter: ReleasesVersionFilter | undefin
return `^${escapedPrefix}${precision}${escapedSuffix}$`;
};
const formatIdentifierName = (identifier: string) => {
const unformattedName = identifier.split("/").pop();
return unformattedName?.replace(/[-_]/g, " ").replace(/(?:^\w|[A-Z]|\b\w)/g, (char) => char.toUpperCase()) ?? "";
};
interface ReleaseEditProps {
fieldPath: string;
repository: ReleasesRepository;
@@ -209,7 +216,7 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
return (
<Stack>
<Group align="center">
<Group align="center" wrap="nowrap">
<Select
withAsterisk
label={tRepository("provider.label")}
@@ -224,6 +231,7 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
handleChange({ providerKey: value });
}
}}
style={{ flex: 1, flexBasis: "40%" }}
/>
<TextInput
@@ -231,10 +239,38 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
label={tRepository("identifier.label")}
value={tempRepository.identifier}
onChange={(event) => {
handleChange({ identifier: event.currentTarget.value });
const name =
tempRepository.name === undefined ||
formatIdentifierName(tempRepository.identifier) === tempRepository.name
? formatIdentifierName(event.currentTarget.value)
: tempRepository.name;
handleChange({
identifier: event.currentTarget.value,
name,
});
}}
error={formErrors[`${innerProps.fieldPath}.identifier`]}
style={{ flex: 1 }}
w="100%"
/>
</Group>
<Group align="center" wrap="nowrap">
<TextInput
label={tRepository("name.label")}
value={tempRepository.name ?? ""}
onChange={(event) => {
handleChange({ name: event.currentTarget.value });
}}
error={formErrors[`${innerProps.fieldPath}.name`]}
style={{ flex: 1, flexBasis: "40%" }}
/>
<IconPicker
withAsterisk={false}
value={tempRepository.iconUrl}
onChange={(url) => handleChange({ iconUrl: url })}
error={formErrors[`${innerProps.fieldPath}.iconUrl`] as string}
/>
</Group>
@@ -298,13 +334,6 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
</Text>
</Fieldset>
<IconPicker
withAsterisk={false}
value={tempRepository.iconUrl}
onChange={(url) => handleChange({ iconUrl: url })}
error={formErrors[`${innerProps.fieldPath}.iconUrl`] as string}
/>
<Divider my={"sm"} />
<Group justify="flex-end">
<Button variant="default" onClick={actions.closeModal} color="gray.5">

View File

@@ -62,7 +62,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
);
const repositories = useMemo(() => {
return results
const formattedResults = results
.flat()
.map(({ data }) => {
if (data === undefined) return undefined;
@@ -74,8 +74,8 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
if (repository === undefined) return undefined;
return {
...repository,
...data,
iconUrl: repository.iconUrl,
isNewRelease:
relativeDateOptions.newReleaseWithin !== "" && data.latestReleaseAt
? isDateWithin(data.latestReleaseAt, relativeDateOptions.newReleaseWithin)
@@ -99,10 +99,17 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
if (repoB?.latestReleaseAt === undefined) return -1;
return repoA.latestReleaseAt > repoB.latestReleaseAt ? -1 : 1;
}) as ReleasesRepositoryResponse[];
if (typeof options.topReleases !== "string" && options.topReleases > 0) {
return formattedResults.slice(0, options.topReleases);
}
return formattedResults;
}, [
results,
options.repositories,
options.showOnlyHighlighted,
options.topReleases,
relativeDateOptions.newReleaseWithin,
relativeDateOptions.staleReleaseWithin,
]);
@@ -152,7 +159,8 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
/>
<Group gap={5} justify="space-between" style={{ flex: 1 }}>
<Text size="xs">{repository.identifier}</Text>
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
<Text size="xs">{repository.name || repository.identifier}</Text>
<Tooltip
withArrow
@@ -346,6 +354,11 @@ const ExpandedDisplay = ({ repository, hasIconColor }: ExtendedDisplayProps) =>
</Text>
)}
</Group>
<Text size="xs" c="iconColor" ff="monospace">
{repository.identifier}
</Text>
{(repository.releaseUrl ?? repository.projectUrl) && (
<>
<Divider my={10} mx="30%" />

View File

@@ -30,12 +30,18 @@ export const { definition, componentLoader } = createWidgetDefinition("releases"
showDetails: factory.switch({
defaultValue: true,
}),
topReleases: factory.number({
withDescription: true,
defaultValue: 0,
validate: z.number().min(0),
}),
repositories: factory.multiReleasesRepositories({
defaultValue: [],
validate: z.array(
z.object({
providerKey: z.string().min(1),
identifier: z.string().min(1),
name: z.string().optional(),
versionFilter: z
.object({
prefix: z.string().optional(),

View File

@@ -7,6 +7,7 @@ export interface ReleasesVersionFilter {
export interface ReleasesRepository {
providerKey: string;
identifier: string;
name?: string;
versionFilter?: ReleasesVersionFilter;
iconUrl?: string;
}