Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

20 load list of organizations on the left cell #21

Merged
merged 8 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.0",
"react-table": "^7.8.0",
"styled-components": "^6.1.9",
"web-vitals": "^2.1.4",
"zustand": "^4.5.2"
Expand Down
1 change: 1 addition & 0 deletions ui/src/api/organizations/github/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useOrganizations } from './use-organizations';
20 changes: 20 additions & 0 deletions ui/src/api/organizations/github/use-organizations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '@/api';
import { useAuthStore } from '@/store';
import { organizationQueryKeys } from '../query-keys';

export const getOrganizationsFn = async () => {
const response = await apiClient.get('/user/repos', {
headers: {
Authorization: `Bearer ${useAuthStore.getState().token}`,
},
});

return response.data;
};

export const useOrganizations = () =>
useQuery({
queryKey: organizationQueryKeys.all,
queryFn: getOrganizationsFn,
});
1 change: 1 addition & 0 deletions ui/src/api/organizations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useOrganizations } from './github';
5 changes: 5 additions & 0 deletions ui/src/api/organizations/query-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const organizationQueryKeys = {
all: ['organizations'],
details: () => [...organizationQueryKeys.all, 'detail'],
detail: (id: string) => [...organizationQueryKeys.details(), id],
};
2 changes: 2 additions & 0 deletions ui/src/api/repos/github/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { useRepos } from './use-repos';
export { useRepo } from './use-repo';
export { useRepoFile } from './use-repo-file';
2 changes: 1 addition & 1 deletion ui/src/api/repos/github/repos.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface Repos {
interface GitHubRepo {
id: number;
node_id: string;
name: string;
Expand Down
33 changes: 33 additions & 0 deletions ui/src/api/repos/github/use-repo-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import yaml from 'js-yaml';
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '@/api';
import { useAuthStore } from '@/store';
import { repoQueryKeys } from '@/api/repos/repo-query-keys';

export const readYamlFile = async (owner: any, repoName: any, path: any) => {
const response = await apiClient.get(`repos/${owner}/${repoName}/contents/${path}`, {
headers: {
Authorization: `Bearer ${useAuthStore.getState().token}`,
},
});
const { content, encoding } = response.data;

if (encoding !== 'base64') {
throw new Error('Unsupported encoding');
}

const decodedContent = atob(content);
return yaml.load(decodedContent);
};

export const useRepoFile = (
owner: string | undefined,
repoName: string | undefined,
path: string | undefined,
isEnabled = true
) =>
useQuery({
queryKey: repoQueryKeys.detail(['file', owner, repoName, path]),
queryFn: () => readYamlFile(owner, repoName, path),
enabled: isEnabled && !!owner && !!repoName && !!path,
});
72 changes: 72 additions & 0 deletions ui/src/api/repos/github/use-repo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '@/api';
import { useAuthStore } from '@/store';
import { repoQueryKeys } from '@/api/repos/repo-query-keys';

export const getRepoFn = async (
owner: string | undefined,
repoName: string | undefined,
path = ''
) => {
const response = await apiClient.get(`repos/${owner}/${repoName}/contents/${path}`, {
headers: {
Authorization: `Bearer ${useAuthStore.getState().token}`,
},
});

return response.data;
};

export const getAllYamlFilesFn = async (
owner: string | undefined,
repoName: string | undefined,
initialPath = ''
) => {
async function fetchDirectory(path: any) {
try {
const response = await apiClient.get(`repos/${owner}/${repoName}/contents/${path}`, {
headers: {
Authorization: `Bearer ${useAuthStore.getState().token}`,
},
});
return response.data;
} catch (error) {
console.log(error);
throw error;
}
}

const fetchRecursive = async (path: string) => {
const items = await fetchDirectory(path);
const files = items.filter(
(item: { type: string; name: string }) => item.type === 'file' && item.name.endsWith('.yaml')
);
const directories = items.filter((item: { type: string }) => item.type === 'dir');

const directoryPromises = directories.map((directory: { path: string }) =>
fetchRecursive(directory.path)
);
const results = await Promise.all(directoryPromises);
return files.concat(results.flat());
};

return fetchRecursive(initialPath);
};

export const useRepo = (
owner: string | undefined,
repoName: string | undefined,
path = '',
isEnabled = true
) =>
useQuery({
queryKey: repoQueryKeys.detail([owner, repoName, path]),
queryFn: () => getAllYamlFilesFn(owner, repoName, path),
enabled: isEnabled && !!owner && !!repoName,
});

// function filterYAMLFiles(data: any) {
// return data.filter(
// (item: any) => item.type === 'dir' || (item.type === 'file' && item.name.endsWith('.yaml'))
// );
// }
9 changes: 3 additions & 6 deletions ui/src/api/repos/github/use-repos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,17 @@ import { apiClient } from '@/api';
import { repoQueryKeys } from '../repo-query-keys';

export const getReposFn = async () => {
const { username } = useAuthStore.getState();

const response = await apiClient.get(`/users/${username || ''}/repos`, {
const response = await apiClient.get('/user/repos', {
headers: {
Authorization: `Bearer ${useAuthStore.getState().token}`,
},
});

return response.data as Repos[];
return response.data as Repo[];
};

export const useRepos = () =>
useQuery({
queryKey: repoQueryKeys.details(),
queryKey: repoQueryKeys.all,
queryFn: getReposFn,
retry: 4,
});
2 changes: 1 addition & 1 deletion ui/src/api/repos/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { useRepos } from './github';
export * from './github';
3 changes: 1 addition & 2 deletions ui/src/api/repos/repo-query-keys.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export const repoQueryKeys = {
all: ['repos'],
details: () => [...repoQueryKeys.all, 'details'],
detail: (id: string) => [...repoQueryKeys.details(), id],
detail: (params: any[]) => [...repoQueryKeys.all, ...params],
};
1 change: 1 addition & 0 deletions ui/src/api/repos/repos.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
type Repo = GitHubRepo;
82 changes: 82 additions & 0 deletions ui/src/components/FilesList/FilesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { List, Paper, ScrollArea, Text, Title, Tooltip } from '@mantine/core';
import { useRepoStore } from '@/store';
import { useRepo } from '@/api';
import { Loading } from '@/components';

export const FilesList = () => {
const { selectedRepo, selectFile } = useRepoStore((state) => ({
selectedRepo: state.selectedRepo,
selectFile: state.selectFile,
}));

const { data, isLoading, isError } = useRepo(
selectedRepo?.owner?.login,
selectedRepo?.name,
'',
true
);

if (isLoading) {
return <Loading />;
}

if (isError) {
return <div>Error</div>;
}

return (
<Paper shadow="sm" radius="md" p="md" withBorder>
<Title order={3} style={{ marginBottom: 20 }}>
YAML Files
</Title>
<ScrollArea style={{ height: 300 }}>
<List spacing="sm" size="sm" center>
{data?.map((file: any, index: number) => (
<List.Item key={index} onClick={() => selectFile(file)}>
<Tooltip label={file.path} position="bottom" withArrow>
<Text style={{ cursor: 'pointer' }}>{file.name}</Text>
</Tooltip>
</List.Item>
))}
</List>
</ScrollArea>
</Paper>
);
};
//
// const NestedList = ({ item }) => {
// const { selectedRepo } = useRepoStore();
// const [isOpen, setIsOpen] = useState(false);
// const hasChildren = item.type === 'dir';
//
// const { data, isLoading, isError } = useRepo(
// selectedRepo?.owner?.login,
// selectedRepo?.name,
// item.path,
// isOpen && hasChildren
// );
//
// const toggle = () => setIsOpen(!isOpen);
//
// return (
// <div>
// <Group position="apart" style={{ cursor: 'pointer', alignItems: 'center' }} onClick={toggle}>
// <Text>{item.name}</Text>
// {hasChildren && (
// <ThemeIcon size="sm">
// <IconChevronRight style={{ transform: isOpen ? 'rotate(90deg)' : 'rotate(0deg)' }} />
// </ThemeIcon>
// )}
// </Group>
// {isLoading && <Loader />}
// {isError && <Text color="red">Failed to load data</Text>}
// {isOpen && hasChildren && (
// <Collapse in={isOpen}>
// <Group direction="column" spacing="xs">
// {data?.map((child: any) => <NestedList key={child.sha} item={child} />)}
// </Group>
// </Collapse>
// )}
// </div>
// );
// };
1 change: 1 addition & 0 deletions ui/src/components/FilesList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FilesList } from './FilesList';
18 changes: 14 additions & 4 deletions ui/src/components/Navigator/Navigator.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react';

import { AppShell, Container, Grid, Skeleton } from '@mantine/core';
import { AppShell, Container, Grid, Skeleton, Stack } from '@mantine/core';
import { Header } from '@/components';
import { ReposList } from '../ReposList';
import { FilesList } from '../FilesList';
import { RulesTable } from '@/components/RulesTable/RulesTable';

const child = <Skeleton height="900" radius="md" animate={false} />;

Expand All @@ -13,9 +16,16 @@ export const Navigator = () => (
<AppShell.Main>
<Container fluid mt={50}>
<Grid>
<Grid.Col span={3}>{child}</Grid.Col>
<Grid.Col span={6}>{child}</Grid.Col>
<Grid.Col span={3}>{child}</Grid.Col>
<Grid.Col span={2}>
<Stack>
<ReposList />
<FilesList />
</Stack>
</Grid.Col>
<Grid.Col span={8}>
<RulesTable />
</Grid.Col>
<Grid.Col span={2}>{child}</Grid.Col>
</Grid>
</Container>
</AppShell.Main>
Expand Down
31 changes: 31 additions & 0 deletions ui/src/components/ReposList/ReposList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Avatar, NavLink } from '@mantine/core';
import { useRepos } from '@/api';
import { Loading } from '@/components';
import { useRepoStore } from '@/store';

export const ReposList = () => {
const { data, isLoading, isError } = useRepos();
const { selectedRepo, selectRepo } = useRepoStore();

if (isLoading) {
return <Loading />;
}

if (isError) {
return <div>Error</div>;
}

return (
<>
{data?.map((repo: Repo) => (
<NavLink
label={repo.name}
leftSection={<Avatar src={repo.owner.avatar_url} size="xs" radius="xl" />}
key={repo.name}
onClick={() => selectRepo(repo)}
active={selectedRepo?.name === repo.name}
/>
))}
</>
);
};
1 change: 1 addition & 0 deletions ui/src/components/ReposList/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ReposList } from './ReposList';
Loading
Loading