Provisioning: View in repository open containing folder (#114513)
* Provisioning: View in repostiory open containing folder * i18n * comment * tweaks * Simplify for display --------- Co-authored-by: Clarity-89 <homes89@ukr.net>
This commit is contained in:
co-authored by
Clarity-89
parent
cb05a4ae1b
commit
b473524787
@@ -4,7 +4,7 @@ import { Trans } from '@grafana/i18n';
|
||||
import { LinkButton, Stack, Text, TextLink } from '@grafana/ui';
|
||||
import { useGetRepositoryQuery } from 'app/api/clients/provisioning/v0alpha1';
|
||||
|
||||
import { getRepoHref } from '../utils/git';
|
||||
import { getRepoHrefForProvider } from '../utils/git';
|
||||
|
||||
type RepositoryLinkProps = {
|
||||
name?: string;
|
||||
@@ -19,7 +19,7 @@ export function RepositoryLink({ name, jobType }: RepositoryLinkProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const repoHref = getRepoHref(repo.spec?.github);
|
||||
const repoHref = getRepoHrefForProvider(repo.spec);
|
||||
|
||||
if (jobType === 'sync') {
|
||||
return (
|
||||
|
||||
+6
-16
@@ -2,12 +2,13 @@ import { ReactNode } from 'react';
|
||||
|
||||
import { t, Trans } from '@grafana/i18n';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { Stack, Text, TextLink, Icon, Card, LinkButton, Badge } from '@grafana/ui';
|
||||
import { Badge, Card, LinkButton, Stack, Text, TextLink } from '@grafana/ui';
|
||||
import { Repository, ResourceCount } from 'app/api/clients/provisioning/v0alpha1';
|
||||
|
||||
import { RepoIcon } from '../Shared/RepoIcon';
|
||||
import { StatusBadge } from '../Shared/StatusBadge';
|
||||
import { PROVISIONING_URL } from '../constants';
|
||||
import { getRepoHrefForProvider } from '../utils/git';
|
||||
import { getIsReadOnlyWorkflows } from '../utils/repository';
|
||||
|
||||
import { SyncRepository } from './SyncRepository';
|
||||
@@ -16,7 +17,7 @@ interface Props {
|
||||
repository: Repository;
|
||||
}
|
||||
|
||||
export function RepositoryCard({ repository }: Props) {
|
||||
export function RepositoryListItem({ repository }: Props) {
|
||||
const isReadOnlyRepo = getIsReadOnlyWorkflows(repository.spec?.workflows);
|
||||
const { metadata, spec, status } = repository;
|
||||
const name = metadata?.name ?? '';
|
||||
@@ -27,24 +28,13 @@ export function RepositoryCard({ repository }: Props) {
|
||||
if (spec?.type === 'github') {
|
||||
const { url = '', branch } = spec.github ?? {};
|
||||
const branchUrl = branch ? `${url}/tree/${branch}` : url;
|
||||
const href = getRepoHrefForProvider(spec) || branchUrl;
|
||||
|
||||
meta.push(
|
||||
<TextLink key="link" external href={branchUrl}>
|
||||
{branchUrl}
|
||||
<TextLink key="link" external href={href}>
|
||||
{href.split('/').slice(3).join('/')}
|
||||
</TextLink>
|
||||
);
|
||||
|
||||
if (status?.webhook?.id) {
|
||||
const webhookUrl = `${url}/settings/hooks/${status.webhook.id}`;
|
||||
meta.push(
|
||||
<Stack gap={1} direction="row" alignItems="center">
|
||||
<TextLink key="webhook" href={webhookUrl}>
|
||||
<Trans i18nKey="provisioning.repository-card.get-repository-meta.webhook">Webhook</Trans>
|
||||
</TextLink>
|
||||
<Icon name="check" className="text-success" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
} else if (spec?.type === 'local') {
|
||||
meta.push(
|
||||
<Text variant="bodySmall" key="path">
|
||||
@@ -4,7 +4,7 @@ import { t, Trans } from '@grafana/i18n';
|
||||
import { Alert, Box, EmptyState, FilterInput, Icon, Stack, TextLink } from '@grafana/ui';
|
||||
import { Repository } from 'app/api/clients/provisioning/v0alpha1';
|
||||
|
||||
import { RepositoryCard } from '../Repository/RepositoryCard';
|
||||
import { RepositoryListItem } from '../Repository/RepositoryListItem';
|
||||
import { useResourceStats } from '../Wizard/hooks/useResourceStats';
|
||||
import { UPGRADE_URL } from '../constants';
|
||||
import { useIsProvisionedInstance } from '../hooks/useIsProvisionedInstance';
|
||||
@@ -89,7 +89,7 @@ export function RepositoryList({ items }: Props) {
|
||||
)}
|
||||
<Stack direction={'column'} gap={2}>
|
||||
{filteredItems.length ? (
|
||||
filteredItems.map((item) => <RepositoryCard key={item.metadata?.name} repository={item} />)
|
||||
filteredItems.map((item) => <RepositoryListItem key={item.metadata?.name} repository={item} />)
|
||||
) : (
|
||||
<EmptyState
|
||||
variant="not-found"
|
||||
|
||||
@@ -27,41 +27,81 @@ export const getRepoHref = (github?: RepositorySpec['github']) => {
|
||||
return `${github.url}/tree/${github.branch}`;
|
||||
};
|
||||
|
||||
// Remove leading and trailing slashes from a string.
|
||||
const stripSlashes = (s: string) => s.replace(/^\/+|\/+$/g, '');
|
||||
|
||||
// Split a path into segments and URL-encode each segment.
|
||||
// Ensures the final URL remains valid for all providers (GitHub, GitLab, etc.).
|
||||
const splitAndEncode = (s: string) => stripSlashes(s).split('/').map(encodeURIComponent);
|
||||
|
||||
type BuildRepoUrlParams = {
|
||||
baseUrl?: string;
|
||||
branch?: string | null;
|
||||
providerSegments: string[];
|
||||
path?: string | null;
|
||||
};
|
||||
|
||||
const buildRepoUrl = ({ baseUrl, branch, providerSegments, path }: BuildRepoUrlParams) => {
|
||||
if (!baseUrl) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Normalize base URL: trim whitespace + remove trailing slashes.
|
||||
const cleanBase = stripSlashes(baseUrl.trim());
|
||||
const cleanBranch = branch?.trim() || undefined;
|
||||
|
||||
// Start composing URL parts:
|
||||
// base URL + provider-specific segments (e.g., "tree", "blob", etc.)
|
||||
const parts = [cleanBase, ...providerSegments];
|
||||
|
||||
// Append the branch name if present.
|
||||
if (cleanBranch) {
|
||||
parts.push(cleanBranch);
|
||||
}
|
||||
|
||||
// Append encoded path segments if provided.
|
||||
// This ensures nested files like "src/utils/index.ts" produce safe URLs.
|
||||
if (path) {
|
||||
parts.push(...splitAndEncode(path.trim()));
|
||||
}
|
||||
|
||||
return parts.join('/');
|
||||
};
|
||||
|
||||
export const getRepoHrefForProvider = (spec?: RepositorySpec) => {
|
||||
if (!spec || !spec.type) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (spec.type) {
|
||||
case 'github': {
|
||||
const url = spec.github?.url;
|
||||
const branch = spec.github?.branch;
|
||||
if (!url) {
|
||||
return undefined;
|
||||
}
|
||||
return branch ? `${url}/tree/${branch}` : url;
|
||||
}
|
||||
|
||||
case 'gitlab': {
|
||||
const url = spec.gitlab?.url;
|
||||
const branch = spec.gitlab?.branch;
|
||||
if (!url) {
|
||||
return undefined;
|
||||
}
|
||||
return branch ? `${url}/-/tree/${branch}` : url;
|
||||
}
|
||||
case 'bitbucket': {
|
||||
const url = spec.bitbucket?.url;
|
||||
const branch = spec.bitbucket?.branch;
|
||||
if (!url) {
|
||||
return undefined;
|
||||
}
|
||||
return branch ? `${url}/src/${branch}` : url;
|
||||
}
|
||||
case 'git': {
|
||||
// Return a generic URL for pure git repositories
|
||||
return spec.git?.url;
|
||||
}
|
||||
case 'github':
|
||||
return buildRepoUrl({
|
||||
baseUrl: spec.github?.url,
|
||||
branch: spec.github?.branch,
|
||||
providerSegments: ['tree'],
|
||||
path: spec.github?.path,
|
||||
});
|
||||
case 'gitlab':
|
||||
return buildRepoUrl({
|
||||
baseUrl: spec.gitlab?.url,
|
||||
branch: spec.gitlab?.branch,
|
||||
providerSegments: ['-', 'tree'],
|
||||
path: spec.gitlab?.path,
|
||||
});
|
||||
case 'bitbucket':
|
||||
return buildRepoUrl({
|
||||
baseUrl: spec.bitbucket?.url,
|
||||
branch: spec.bitbucket?.branch,
|
||||
providerSegments: ['src'],
|
||||
path: spec.bitbucket?.path,
|
||||
});
|
||||
case 'git':
|
||||
return buildRepoUrl({
|
||||
baseUrl: spec.git?.url,
|
||||
branch: spec.git?.branch,
|
||||
providerSegments: ['tree'],
|
||||
path: spec.git?.path,
|
||||
});
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -11920,9 +11920,6 @@
|
||||
"source-code": "Source code"
|
||||
},
|
||||
"repository-card": {
|
||||
"get-repository-meta": {
|
||||
"webhook": "Webhook"
|
||||
},
|
||||
"read-only-badge": "Read only",
|
||||
"settings": "Settings",
|
||||
"view": "View"
|
||||
|
||||
Reference in New Issue
Block a user