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:
@@ -2072,6 +2072,10 @@
|
|||||||
"showDetails": {
|
"showDetails": {
|
||||||
"label": "Show Details"
|
"label": "Show Details"
|
||||||
},
|
},
|
||||||
|
"topReleases": {
|
||||||
|
"label": "Top Releases",
|
||||||
|
"description": "The max number of latest releases to show. Zero means no limit."
|
||||||
|
},
|
||||||
"repositories": {
|
"repositories": {
|
||||||
"label": "Repositories",
|
"label": "Repositories",
|
||||||
"addRRepository": {
|
"addRRepository": {
|
||||||
@@ -2084,6 +2088,9 @@
|
|||||||
"label": "Identifier",
|
"label": "Identifier",
|
||||||
"placeholder": "Name or Owner/Name"
|
"placeholder": "Name or Owner/Name"
|
||||||
},
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "Name"
|
||||||
|
},
|
||||||
"versionFilter": {
|
"versionFilter": {
|
||||||
"label": "Version Filter",
|
"label": "Version Filter",
|
||||||
"prefix": {
|
"prefix": {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const WidgetMultiReleasesRepositoriesInput = ({
|
|||||||
(repository: ReleasesRepository, index: number): FormValidation => {
|
(repository: ReleasesRepository, index: number): FormValidation => {
|
||||||
form.setFieldValue(`options.${property}.${index}.providerKey`, repository.providerKey);
|
form.setFieldValue(`options.${property}.${index}.providerKey`, repository.providerKey);
|
||||||
form.setFieldValue(`options.${property}.${index}.identifier`, repository.identifier);
|
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}.versionFilter`, repository.versionFilter);
|
||||||
form.setFieldValue(`options.${property}.${index}.iconUrl`, repository.iconUrl);
|
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}>
|
<Group justify="space-between" align="center" style={{ flex: 1 }} gap={5}>
|
||||||
<Text size="sm" style={{ flex: 1, whiteSpace: "nowrap" }}>
|
<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>
|
||||||
|
|
||||||
<Text c="dimmed" size="xs" ta="end" style={{ flex: 1, whiteSpace: "nowrap" }}>
|
<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}$`;
|
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 {
|
interface ReleaseEditProps {
|
||||||
fieldPath: string;
|
fieldPath: string;
|
||||||
repository: ReleasesRepository;
|
repository: ReleasesRepository;
|
||||||
@@ -209,7 +216,7 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<Group align="center">
|
<Group align="center" wrap="nowrap">
|
||||||
<Select
|
<Select
|
||||||
withAsterisk
|
withAsterisk
|
||||||
label={tRepository("provider.label")}
|
label={tRepository("provider.label")}
|
||||||
@@ -224,6 +231,7 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
|||||||
handleChange({ providerKey: value });
|
handleChange({ providerKey: value });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
style={{ flex: 1, flexBasis: "40%" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -231,10 +239,38 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
|||||||
label={tRepository("identifier.label")}
|
label={tRepository("identifier.label")}
|
||||||
value={tempRepository.identifier}
|
value={tempRepository.identifier}
|
||||||
onChange={(event) => {
|
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`]}
|
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>
|
</Group>
|
||||||
|
|
||||||
@@ -298,13 +334,6 @@ const ReleaseEditModal = createModal<ReleaseEditProps>(({ innerProps, actions })
|
|||||||
</Text>
|
</Text>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
|
|
||||||
<IconPicker
|
|
||||||
withAsterisk={false}
|
|
||||||
value={tempRepository.iconUrl}
|
|
||||||
onChange={(url) => handleChange({ iconUrl: url })}
|
|
||||||
error={formErrors[`${innerProps.fieldPath}.iconUrl`] as string}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Divider my={"sm"} />
|
<Divider my={"sm"} />
|
||||||
<Group justify="flex-end">
|
<Group justify="flex-end">
|
||||||
<Button variant="default" onClick={actions.closeModal} color="gray.5">
|
<Button variant="default" onClick={actions.closeModal} color="gray.5">
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
|||||||
);
|
);
|
||||||
|
|
||||||
const repositories = useMemo(() => {
|
const repositories = useMemo(() => {
|
||||||
return results
|
const formattedResults = results
|
||||||
.flat()
|
.flat()
|
||||||
.map(({ data }) => {
|
.map(({ data }) => {
|
||||||
if (data === undefined) return undefined;
|
if (data === undefined) return undefined;
|
||||||
@@ -74,8 +74,8 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
|||||||
if (repository === undefined) return undefined;
|
if (repository === undefined) return undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...repository,
|
||||||
...data,
|
...data,
|
||||||
iconUrl: repository.iconUrl,
|
|
||||||
isNewRelease:
|
isNewRelease:
|
||||||
relativeDateOptions.newReleaseWithin !== "" && data.latestReleaseAt
|
relativeDateOptions.newReleaseWithin !== "" && data.latestReleaseAt
|
||||||
? isDateWithin(data.latestReleaseAt, relativeDateOptions.newReleaseWithin)
|
? isDateWithin(data.latestReleaseAt, relativeDateOptions.newReleaseWithin)
|
||||||
@@ -99,10 +99,17 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
|||||||
if (repoB?.latestReleaseAt === undefined) return -1;
|
if (repoB?.latestReleaseAt === undefined) return -1;
|
||||||
return repoA.latestReleaseAt > repoB.latestReleaseAt ? -1 : 1;
|
return repoA.latestReleaseAt > repoB.latestReleaseAt ? -1 : 1;
|
||||||
}) as ReleasesRepositoryResponse[];
|
}) as ReleasesRepositoryResponse[];
|
||||||
|
|
||||||
|
if (typeof options.topReleases !== "string" && options.topReleases > 0) {
|
||||||
|
return formattedResults.slice(0, options.topReleases);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formattedResults;
|
||||||
}, [
|
}, [
|
||||||
results,
|
results,
|
||||||
options.repositories,
|
options.repositories,
|
||||||
options.showOnlyHighlighted,
|
options.showOnlyHighlighted,
|
||||||
|
options.topReleases,
|
||||||
relativeDateOptions.newReleaseWithin,
|
relativeDateOptions.newReleaseWithin,
|
||||||
relativeDateOptions.staleReleaseWithin,
|
relativeDateOptions.staleReleaseWithin,
|
||||||
]);
|
]);
|
||||||
@@ -152,7 +159,8 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Group gap={5} justify="space-between" style={{ flex: 1 }}>
|
<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
|
<Tooltip
|
||||||
withArrow
|
withArrow
|
||||||
@@ -346,6 +354,11 @@ const ExpandedDisplay = ({ repository, hasIconColor }: ExtendedDisplayProps) =>
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
<Text size="xs" c="iconColor" ff="monospace">
|
||||||
|
{repository.identifier}
|
||||||
|
</Text>
|
||||||
|
|
||||||
{(repository.releaseUrl ?? repository.projectUrl) && (
|
{(repository.releaseUrl ?? repository.projectUrl) && (
|
||||||
<>
|
<>
|
||||||
<Divider my={10} mx="30%" />
|
<Divider my={10} mx="30%" />
|
||||||
|
|||||||
@@ -30,12 +30,18 @@ export const { definition, componentLoader } = createWidgetDefinition("releases"
|
|||||||
showDetails: factory.switch({
|
showDetails: factory.switch({
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
}),
|
}),
|
||||||
|
topReleases: factory.number({
|
||||||
|
withDescription: true,
|
||||||
|
defaultValue: 0,
|
||||||
|
validate: z.number().min(0),
|
||||||
|
}),
|
||||||
repositories: factory.multiReleasesRepositories({
|
repositories: factory.multiReleasesRepositories({
|
||||||
defaultValue: [],
|
defaultValue: [],
|
||||||
validate: z.array(
|
validate: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
providerKey: z.string().min(1),
|
providerKey: z.string().min(1),
|
||||||
identifier: z.string().min(1),
|
identifier: z.string().min(1),
|
||||||
|
name: z.string().optional(),
|
||||||
versionFilter: z
|
versionFilter: z
|
||||||
.object({
|
.object({
|
||||||
prefix: z.string().optional(),
|
prefix: z.string().optional(),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface ReleasesVersionFilter {
|
|||||||
export interface ReleasesRepository {
|
export interface ReleasesRepository {
|
||||||
providerKey: string;
|
providerKey: string;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
|
name?: string;
|
||||||
versionFilter?: ReleasesVersionFilter;
|
versionFilter?: ReleasesVersionFilter;
|
||||||
iconUrl?: string;
|
iconUrl?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user