fix: improved import from docker parsing (#3642)

This commit is contained in:
Andre Silva
2025-07-19 20:22:11 +01:00
committed by GitHub
parent 18c3ed559e
commit 36b0f576e5

View File

@@ -509,19 +509,19 @@ interface ReleasesRepositoryImport extends ReleasesRepository {
alreadyImported: boolean; alreadyImported: boolean;
} }
interface ContainerImageSelectorProps { interface ImportRepositorySelectProps {
containerImage: ReleasesRepositoryImport; repository: ReleasesRepositoryImport;
integration?: Integration; integration?: Integration;
versionFilterPrecisionOptions: string[]; versionFilterPrecisionOptions: string[];
onImageSelectionChanged?: (isSelected: boolean) => void; onImageSelectionChanged?: (isSelected: boolean) => void;
} }
const ContainerImageSelector = ({ const ImportRepositorySelect = ({
containerImage, repository,
integration, integration,
versionFilterPrecisionOptions, versionFilterPrecisionOptions,
onImageSelectionChanged, onImageSelectionChanged,
}: ContainerImageSelectorProps) => { }: ImportRepositorySelectProps) => {
const tRepository = useScopedI18n("widget.releases.option.repositories"); const tRepository = useScopedI18n("widget.releases.option.repositories");
const checkBoxProps: CheckboxProps = !onImageSelectionChanged const checkBoxProps: CheckboxProps = !onImageSelectionChanged
? { ? {
@@ -539,29 +539,29 @@ const ContainerImageSelector = ({
label={ label={
<Group> <Group>
<Image <Image
src={containerImage.iconUrl} src={repository.iconUrl}
style={{ style={{
height: "1.2em", height: "1.2em",
width: "1.2em", width: "1.2em",
}} }}
/> />
<Text>{containerImage.identifier}</Text> <Text>{repository.identifier}</Text>
</Group> </Group>
} }
{...checkBoxProps} {...checkBoxProps}
/> />
{containerImage.versionFilter && ( {repository.versionFilter && (
<Group gap={5}> <Group gap={5}>
<Text c="dimmed" size="xs"> <Text c="dimmed" size="xs">
{tRepository("versionFilter.label")}: {tRepository("versionFilter.label")}:
</Text> </Text>
<Code>{containerImage.versionFilter.prefix && containerImage.versionFilter.prefix}</Code> <Code>{repository.versionFilter.prefix && repository.versionFilter.prefix}</Code>
<Code color="var(--mantine-primary-color-light)" fw={700}> <Code color="var(--mantine-primary-color-light)" fw={700}>
{versionFilterPrecisionOptions[containerImage.versionFilter.precision]} {versionFilterPrecisionOptions[repository.versionFilter.precision]}
</Code> </Code>
<Code>{containerImage.versionFilter.suffix && containerImage.versionFilter.suffix}</Code> <Code>{repository.versionFilter.suffix && repository.versionFilter.suffix}</Code>
</Group> </Group>
)} )}
</Group> </Group>
@@ -610,36 +610,47 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
enabled: innerProps.isAdmin, enabled: innerProps.isAdmin,
}); });
const containersImages: ReleasesRepositoryImport[] = useMemo( const importRepositories: ReleasesRepositoryImport[] = useMemo(
() => () =>
docker.data?.containers.reduce<ReleasesRepositoryImport[]>((acc, containerImage) => { docker.data?.containers.reduce<ReleasesRepositoryImport[]>((acc, container) => {
const imageParts = containerImage.image.split("/"); const [maybeSource, maybeIdentifierAndVersion] = container.image.split(/\/(.*)/);
const source = imageParts.length > 1 ? imageParts[0] : "docker.io"; const hasSource = maybeSource && maybeSource in sourceToProviderKind;
const identifierImage = imageParts.length > 1 ? imageParts[1] : imageParts[0]; const source = hasSource ? maybeSource : "docker.io";
const identifierAndVersion = hasSource ? maybeIdentifierAndVersion : container.image;
if (!source || !identifierImage) return acc; if (!identifierAndVersion) return acc;
const providerKey = source in containerImageToProviderKind ? containerImageToProviderKind[source] : "dockerHub"; const providerKey = sourceToProviderKind[source];
const integrationId = Object.values(innerProps.integrations).find( const integrationId = Object.values(innerProps.integrations).find(
(integration) => integration.kind === providerKey, (integration) => integration.kind === providerKey,
)?.id; )?.id;
const [identifier, version] = identifierImage.split(":"); const [identifier, version] = identifierAndVersion.split(":");
if (!identifier || !integrationId) return acc; if (!identifier || !integrationId) return acc;
if (acc.some((item) => item.providerIntegrationId === integrationId && item.identifier === identifier)) if (
acc.some(
(item) =>
item.providerIntegrationId !== undefined &&
innerProps.integrations[item.providerIntegrationId]?.kind === providerKey &&
item.identifier === identifier,
)
)
return acc; return acc;
acc.push({ acc.push({
id: createId(), id: createId(),
providerIntegrationId: integrationId, providerIntegrationId: integrationId,
identifier, identifier,
iconUrl: containerImage.iconUrl ?? undefined, iconUrl: container.iconUrl ?? undefined,
name: formatIdentifierName(identifier), name: formatIdentifierName(identifier),
versionFilter: version ? parseImageVersionToVersionFilter(version) : undefined, versionFilter: version ? parseImageVersionToVersionFilter(version) : undefined,
alreadyImported: innerProps.repositories.some( alreadyImported: innerProps.repositories.some(
(item) => item.providerIntegrationId === integrationId && item.identifier === identifier, (item) =>
item.providerIntegrationId !== undefined &&
innerProps.integrations[item.providerIntegrationId]?.kind === providerKey &&
item.identifier === identifier,
), ),
}); });
return acc; return acc;
@@ -657,13 +668,13 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
}, [innerProps, selectedImages, actions]); }, [innerProps, selectedImages, actions]);
const allImagesImported = useMemo( const allImagesImported = useMemo(
() => containersImages.every((containerImage) => containerImage.alreadyImported), () => importRepositories.every((repository) => repository.alreadyImported),
[containersImages], [importRepositories],
); );
const anyImagesImported = useMemo( const anyImagesImported = useMemo(
() => containersImages.some((containerImage) => containerImage.alreadyImported), () => importRepositories.some((repository) => repository.alreadyImported),
[containersImages], [importRepositories],
); );
return ( return (
@@ -673,7 +684,7 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
<Loader size="xl" /> <Loader size="xl" />
<Title order={3}>{tRepository("importRepositories.loading")}</Title> <Title order={3}>{tRepository("importRepositories.loading")}</Title>
</Stack> </Stack>
) : containersImages.length === 0 ? ( ) : importRepositories.length === 0 ? (
<Stack justify="center" align="center"> <Stack justify="center" align="center">
<IconBrandDocker stroke={1} size={128} /> <IconBrandDocker stroke={1} size={128} />
<Title order={3}>{tRepository("importRepositories.noImagesFound")}</Title> <Title order={3}>{tRepository("importRepositories.noImagesFound")}</Title>
@@ -694,23 +705,23 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
</Accordion.Control> </Accordion.Control>
<Accordion.Panel> <Accordion.Panel>
{!allImagesImported && {!allImagesImported &&
containersImages importRepositories
.filter((containerImage) => !containerImage.alreadyImported) .filter((repository) => !repository.alreadyImported)
.map((containerImage) => { .map((repository) => {
const integration = containerImage.providerIntegrationId const integration = repository.providerIntegrationId
? innerProps.integrations[containerImage.providerIntegrationId] ? innerProps.integrations[repository.providerIntegrationId]
: undefined; : undefined;
return ( return (
<ContainerImageSelector <ImportRepositorySelect
key={containerImage.id} key={repository.id}
containerImage={containerImage} repository={repository}
integration={integration} integration={integration}
versionFilterPrecisionOptions={innerProps.versionFilterPrecisionOptions} versionFilterPrecisionOptions={innerProps.versionFilterPrecisionOptions}
onImageSelectionChanged={(isSelected) => onImageSelectionChanged={(isSelected) =>
isSelected isSelected
? setSelectedImages([...selectedImages, containerImage]) ? setSelectedImages([...selectedImages, repository])
: setSelectedImages(selectedImages.filter((img) => img !== containerImage)) : setSelectedImages(selectedImages.filter((img) => img !== repository))
} }
/> />
); );
@@ -723,17 +734,17 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
</Accordion.Control> </Accordion.Control>
<Accordion.Panel> <Accordion.Panel>
{anyImagesImported && {anyImagesImported &&
containersImages importRepositories
.filter((containerImage) => containerImage.alreadyImported) .filter((repository) => repository.alreadyImported)
.map((containerImage) => { .map((repository) => {
const integration = containerImage.providerIntegrationId const integration = repository.providerIntegrationId
? innerProps.integrations[containerImage.providerIntegrationId] ? innerProps.integrations[repository.providerIntegrationId]
: undefined; : undefined;
return ( return (
<ContainerImageSelector <ImportRepositorySelect
key={containerImage.id} key={repository.id}
containerImage={containerImage} repository={repository}
integration={integration} integration={integration}
versionFilterPrecisionOptions={innerProps.versionFilterPrecisionOptions} versionFilterPrecisionOptions={innerProps.versionFilterPrecisionOptions}
/> />
@@ -763,7 +774,7 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
size: "xl", size: "xl",
}); });
const containerImageToProviderKind: Record<string, IntegrationKind> = { const sourceToProviderKind: Record<string, IntegrationKind> = {
"ghcr.io": "github", "ghcr.io": "github",
"docker.io": "dockerHub", "docker.io": "dockerHub",
}; };