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:
Yunwen Zheng
2025-11-27 07:01:40 +00:00
committed by GitHub
co-authored by Clarity-89
parent cb05a4ae1b
commit b473524787
5 changed files with 79 additions and 52 deletions
@@ -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 (
@@ -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"
+69 -29
View File
@@ -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;
}
-3
View File
@@ -11920,9 +11920,6 @@
"source-code": "Source code"
},
"repository-card": {
"get-repository-meta": {
"webhook": "Webhook"
},
"read-only-badge": "Read only",
"settings": "Settings",
"view": "View"