diff --git a/ui-cra/package.json b/ui-cra/package.json
index 8b98d0c534..cc9d5307e8 100644
--- a/ui-cra/package.json
+++ b/ui-cra/package.json
@@ -23,6 +23,7 @@
"@types/react-router-dom": "^5.1.7",
"@types/react-syntax-highlighter": "^13.5.2",
"@types/styled-components": "^5.1.9",
+ "@types/urijs": "^1.19.19",
"@weaveworks/progressive-delivery": "0.0.0-rc13",
"@weaveworks/weave-gitops": "npm:@weaveworks/weave-gitops-main@0.20.0-7-gb6d3961f",
"@weaveworks/weave-gitops-main": "0.20.0-12-gccf5178b",
@@ -53,6 +54,7 @@
"remark-gfm": "^3.0.1",
"styled-components": "^5.3.0",
"typescript": "^4.1.2",
+ "urijs": "^1.19.11",
"web-vitals": "^1.0.1",
"yaml": "^2.2.1"
},
diff --git a/ui-cra/src/components/Applications/__tests__/__snapshots__/index.test.tsx.snap b/ui-cra/src/components/Applications/__tests__/__snapshots__/index.test.tsx.snap
index 2cc0351217..a989e061be 100644
--- a/ui-cra/src/components/Applications/__tests__/__snapshots__/index.test.tsx.snap
+++ b/ui-cra/src/components/Applications/__tests__/__snapshots__/index.test.tsx.snap
@@ -374,31 +374,9 @@ exports[`Applications index test snapshots loading 1`] = `
ADD AN APPLICATION
-
+
+ Loading...
+
Applications
@@ -1251,34 +1229,9 @@ exports[`Applications index test snapshots success 1`] = `
class="MuiTouchRipple-root"
/>
-
+
+ Git Repos not found
+
`
}
`;
-const useStyles = makeStyles(() =>
- createStyles({
- externalIcon: {
- marginRight: theme.spacing.small,
- },
- }),
-);
-
const WGApplicationsDashboard: FC = () => {
const { data: automations, isLoading } = useListAutomations();
const history = useHistory();
- const listConfigContext = useListConfigContext();
- const repoLink = listConfigContext?.repoLink || '';
- const classes = useStyles();
const handleAddApplication = () => history.push(Routes.AddApplication);
@@ -70,14 +56,7 @@ const WGApplicationsDashboard: FC = () => {
>
ADD AN APPLICATION
-
+
{isLoading ? (
diff --git a/ui-cra/src/components/Clusters/OpenedPullRequest.tsx b/ui-cra/src/components/Clusters/OpenedPullRequest.tsx
new file mode 100644
index 0000000000..d8e9350ec7
--- /dev/null
+++ b/ui-cra/src/components/Clusters/OpenedPullRequest.tsx
@@ -0,0 +1,173 @@
+import React, { useMemo } from 'react';
+import {
+ Button,
+ GitRepository,
+ Icon,
+ IconType,
+} from '@weaveworks/weave-gitops';
+import ButtonGroup from '@material-ui/core/ButtonGroup';
+import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
+import ClickAwayListener from '@material-ui/core/ClickAwayListener';
+import Grow from '@material-ui/core/Grow';
+import Paper from '@material-ui/core/Paper';
+import Popper from '@material-ui/core/Popper';
+import MenuItem from '@material-ui/core/MenuItem';
+import MenuList from '@material-ui/core/MenuList';
+import { createStyles, makeStyles } from '@material-ui/core';
+import { openLinkHandler } from '../../utils/link-checker';
+import useConfig from '../../hooks/config';
+import { GetConfigResponse } from '../../cluster-services/cluster_services.pb';
+import {
+ getDefaultGitRepo,
+ getProvider,
+ getRepositoryUrl,
+} from '../Templates/Form/utils';
+import { useGitRepos } from '../../hooks/gitrepos';
+
+const useStyles = makeStyles(() =>
+ createStyles({
+ optionsButton: {
+ marginRight: '0px',
+ },
+ externalLink: {
+ marginRight: '5px',
+ },
+ }),
+);
+
+function getPullRequestUrl(gitRepo: GitRepository, config: GetConfigResponse) {
+ const provider = getProvider(gitRepo, config);
+
+ const baseUrl = getRepositoryUrl(gitRepo);
+ if (provider === 'gitlab') {
+ return baseUrl + '/-/merge_requests';
+ }
+
+ // FIXME: this is not correct
+ if (provider === 'bitbucket-server') {
+ return baseUrl + '/pull-requests';
+ }
+
+ // FIXME: this is not correct
+ if (provider === 'azure-devops') {
+ return baseUrl + '/pullrequests';
+ }
+
+ // github is the default
+ return baseUrl + '/pulls';
+}
+
+export default function OpenedPullRequest() {
+ const [open, setOpen] = React.useState(false);
+ const anchorRef = React.useRef(null);
+
+ const { gitRepos } = useGitRepos();
+
+ const Classes = useStyles();
+
+ const { data: config, isLoading } = useConfig();
+
+ const options = useMemo(
+ () =>
+ !config
+ ? ([] as string[])
+ : gitRepos.map(repo => getPullRequestUrl(repo, config)),
+ [gitRepos, config],
+ );
+
+ if (isLoading) {
+ return Loading...
;
+ }
+
+ if (!config) {
+ return Config not found
;
+ }
+
+ if (!gitRepos || gitRepos.length === 0) {
+ return Git Repos not found
;
+ }
+
+ const defaultRepo = getDefaultGitRepo(gitRepos);
+
+ const handleToggle = () => {
+ setOpen(prevOpen => !prevOpen);
+ };
+
+ const handleClose = (event: React.MouseEvent) => {
+ if (
+ anchorRef.current &&
+ anchorRef.current.contains(event.target as HTMLElement)
+ ) {
+ return;
+ }
+
+ setOpen(false);
+ };
+
+ return (
+ <>
+
+
+
+
+
+ {({ TransitionProps, placement }) => (
+
+
+
+
+
+
+
+ )}
+
+ >
+ );
+}
diff --git a/ui-cra/src/components/Clusters/index.tsx b/ui-cra/src/components/Clusters/index.tsx
index 91f986b0dc..1237212aae 100644
--- a/ui-cra/src/components/Clusters/index.tsx
+++ b/ui-cra/src/components/Clusters/index.tsx
@@ -50,7 +50,6 @@ import {
Rancher,
Vsphere,
} from '../../utils/icons';
-import { openLinkHandler } from '../../utils/link-checker';
import { ContentWrapper } from '../Layout/ContentWrapper';
import { PageTemplate } from '../Layout/PageTemplate';
import PoliciesViolations from '../PolicyViolations';
@@ -66,6 +65,7 @@ import LoadingWrapper from '../Workspaces/WorkspaceDetails/Tabs/WorkspaceTabsWra
import { ConnectClusterDialog } from './ConnectInfoBox';
import { DashboardsList } from './DashboardsList';
import { DeleteClusterDialog } from './Delete';
+import OpenedPullRequest from './OpenedPullRequest';
const ClustersTableWrapper = styled(TableWrapper)`
thead {
@@ -104,9 +104,6 @@ const useStyles = makeStyles(() =>
marginRight: theme.spacing.small,
color: theme.colors.neutral30,
},
- externalIcon: {
- marginRight: theme.spacing.small,
- },
}),
);
@@ -232,9 +229,7 @@ const MCCP: FC<{
() => getGitRepos(sources?.result),
[sources?.result],
);
-
const listConfigContext = useListConfigContext();
- const repoLink = listConfigContext?.repoLink || '';
const provider = listConfigContext?.provider;
const capiClusters = useMemo(
@@ -253,7 +248,6 @@ const MCCP: FC<{
const [random, setRandom] = useState(
Math.random().toString(36).substring(7),
);
- const classes = useStyles();
useEffect(() => {
if (openDeletePR === true) {
@@ -452,14 +446,7 @@ const MCCP: FC<{
onFinish={() => setOpenConnectInfo(false)}
/>
)}
-
+
diff --git a/ui-cra/src/components/Templates/Form/__tests__/utils.test.tsx b/ui-cra/src/components/Templates/Form/__tests__/utils.test.tsx
index b5c50c8406..9187efd680 100644
--- a/ui-cra/src/components/Templates/Form/__tests__/utils.test.tsx
+++ b/ui-cra/src/components/Templates/Form/__tests__/utils.test.tsx
@@ -2,11 +2,12 @@ import { GitRepository } from '@weaveworks/weave-gitops';
import { getInitialGitRepo, getRepositoryUrl } from '../utils';
describe('getRepositoryUrl', () => {
- it('should return nil on a git@github.com: style url as flux does not support these', () => {
+ it("should return something, but we don't care what it is as git@github.com: style url as flux does not support these", () => {
const url = 'git@github.com:org/repo.git';
- expect(getRepositoryUrl({ obj: { spec: { url } } } as GitRepository)).toBe(
- url,
- );
+
+ expect(
+ getRepositoryUrl({ obj: { spec: { url } } } as GitRepository),
+ ).toBeTruthy();
});
it('should normalize ssh/https urls to https preserving .git if present', () => {
diff --git a/ui-cra/src/components/Templates/Form/utils.tsx b/ui-cra/src/components/Templates/Form/utils.tsx
index 9146a3fb14..5ac0ed2b0b 100644
--- a/ui-cra/src/components/Templates/Form/utils.tsx
+++ b/ui-cra/src/components/Templates/Form/utils.tsx
@@ -8,6 +8,8 @@ import { GetTerraformObjectResponse } from '../../../api/terraform/terraform.pb'
import { GitopsClusterEnriched } from '../../../types/custom';
import { Resource } from '../Edit/EditButton';
import GitUrlParse from 'git-url-parse';
+import URI from 'urijs';
+import { GetConfigResponse } from '../../../cluster-services/cluster_services.pb';
const yamlConverter = require('js-yaml');
@@ -50,23 +52,36 @@ export const getCreateRequestAnnotation = (resource: Resource) => {
return maybeParseJSON(getAnnotation(resource));
};
-export const getRepositoryUrl = (repo: GitRepository) => {
+export function getRepositoryUrl(repo: GitRepository) {
// the https url can be overridden with an annotation
const httpsUrl =
repo?.obj?.metadata?.annotations?.['weave.works/repo-https-url'];
if (httpsUrl) {
return httpsUrl;
}
- let repositoryUrl = repo?.obj?.spec?.url;
- let parsedUrl = GitUrlParse(repositoryUrl);
- if (parsedUrl?.protocol === 'ssh') {
- repositoryUrl = parsedUrl.href.replace('ssh://git@', 'https://');
+ let uri = URI(repo?.obj?.spec?.url);
+ if (uri.hostname() === 'ssh.dev.azure.com') {
+ uri = azureSshToHttps(uri.toString());
}
- // flux does not support "git@github.com:org/repo.git" style urls
- // so we return the original url, the BE handler will fail and return
- // an error to the user
- return repositoryUrl;
-};
+ return uri.protocol('https').port('').userinfo('').toString();
+}
+
+function azureSshToHttps(sshUrl: string) {
+ const parts = sshUrl.split('/');
+ const organization = parts[4];
+ const project = parts[5];
+ const repository = parts[6];
+
+ const httpsUrl = `https://dev.azure.com/${organization}/${project}/_git/${repository}`;
+
+ return URI(httpsUrl);
+}
+
+export function getProvider(repo: GitRepository, config: GetConfigResponse) {
+ const url = getRepositoryUrl(repo);
+ const domain = URI(url).hostname();
+ return config?.gitHostTypes?.[domain] || 'github';
+}
export function getInitialGitRepo(
initialUrl: string | null,
@@ -92,6 +107,10 @@ export function getInitialGitRepo(
}
}
+ return getDefaultGitRepo(gitRepos);
+}
+
+export function getDefaultGitRepo(gitRepos: GitRepository[]) {
const annoRepo = gitRepos.find(
repo =>
repo?.obj?.metadata?.annotations?.['weave.works/repo-role'] === 'default',
@@ -102,9 +121,12 @@ export function getInitialGitRepo(
const mainRepo = gitRepos.find(
repo =>
+ // FIXME: we should also be checking the management cluster name
+ // repo.clusterName === config.managementClusterName &&
repo?.obj?.metadata?.name === 'flux-system' &&
repo?.obj?.metadata?.namespace === 'flux-system',
);
+
if (mainRepo) {
return mainRepo;
}
diff --git a/ui-cra/src/hooks/gitrepos.tsx b/ui-cra/src/hooks/gitrepos.tsx
new file mode 100644
index 0000000000..11aeaa9f7a
--- /dev/null
+++ b/ui-cra/src/hooks/gitrepos.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import _ from 'lodash';
+
+import { Kind, useListSources } from '@weaveworks/weave-gitops';
+import { GitRepository, Source } from '@weaveworks/weave-gitops/ui/lib/objects';
+
+export const getGitRepos = (sources: Source[] | undefined) =>
+ _.orderBy(
+ _.uniqBy(
+ _.filter(
+ sources,
+ (item): item is GitRepository => item.type === Kind.GitRepository,
+ ),
+ repo => repo?.obj?.spec?.url,
+ ),
+ ['name'],
+ ['asc'],
+ );
+
+export const useGitRepos = () => {
+ const { data, error, isLoading } = useListSources('', '', { retry: false });
+ const gitRepos = React.useMemo(
+ () => getGitRepos(data?.result),
+ [data?.result],
+ );
+
+ return { gitRepos, error, isLoading };
+};
diff --git a/ui-cra/src/hooks/versions.ts b/ui-cra/src/hooks/versions.ts
index e7eb5f8ce9..5ec8f64ede 100644
--- a/ui-cra/src/hooks/versions.ts
+++ b/ui-cra/src/hooks/versions.ts
@@ -3,7 +3,6 @@ import { useQuery } from 'react-query';
import { GetConfigResponse } from '../cluster-services/cluster_services.pb';
import { EnterpriseClientContext } from '../contexts/EnterpriseClient';
import { useRequest } from '../contexts/Request';
-import GitUrlParse from 'git-url-parse';
import { GitAuth } from '../contexts/GitAuth';
export function useListVersion() {
@@ -14,14 +13,12 @@ export function useListVersion() {
);
}
export interface ListConfigResponse extends GetConfigResponse {
- repoLink: string;
uiConfig: any;
[key: string]: any;
}
export function useListConfig() {
const { api } = useContext(EnterpriseClientContext);
- const [repoLink, setRepoLink] = useState('');
const [provider, setProvider] = useState('');
const queryResponse = useQuery('config', () =>
api.GetConfig({}),
@@ -33,21 +30,12 @@ export function useListConfig() {
useEffect(() => {
repositoryURL &&
gitAuthClient.ParseRepoURL({ url: repositoryURL }).then(res => {
- const { resource, full_name, protocol } = GitUrlParse(repositoryURL);
setProvider(res.provider || '');
- if (res.provider === 'GitHub') {
- setRepoLink(`${protocol}://${resource}/${full_name}/pulls`);
- } else if (res.provider === 'GitLab') {
- setRepoLink(
- `${protocol}://${resource}/${full_name}/-/merge_requests`,
- );
- }
});
}, [repositoryURL, gitAuthClient]);
return {
...queryResponse,
- repoLink,
uiConfig,
provider,
};
diff --git a/ui-cra/yarn.lock b/ui-cra/yarn.lock
index c5df2f6b2a..fbf27ade51 100644
--- a/ui-cra/yarn.lock
+++ b/ui-cra/yarn.lock
@@ -2224,6 +2224,11 @@
resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
+"@types/urijs@^1.19.19":
+ version "1.19.19"
+ resolved "https://registry.yarnpkg.com/@types/urijs/-/urijs-1.19.19.tgz#2789369799907fc11e2bc6e3a00f6478c2281b95"
+ integrity sha512-FDJNkyhmKLw7uEvTxx5tSXfPeQpO0iy73Ry+PmYZJvQy0QIWX8a7kJ4kLWRf+EbTPJEPDSgPXHaM7pzr5lmvCg==
+
"@types/ws@^8.2.2":
version "8.5.1"
resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.1.tgz"
@@ -10504,6 +10509,11 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
+urijs@^1.19.11:
+ version "1.19.11"
+ resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.11.tgz#204b0d6b605ae80bea54bea39280cdb7c9f923cc"
+ integrity sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==
+
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"