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

namespace list view with new api #657

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
771dd37
update ns type interface
ShaiahWren Jun 26, 2023
e3b3a24
implement new api for list view.
ShaiahWren Jun 26, 2023
d072c9b
fix icontains filter
ShaiahWren Jun 26, 2023
2579075
rebase
ShaiahWren Jun 26, 2023
4c34801
use pulpAI in place of hubAPI
ShaiahWren Jun 26, 2023
b365800
use pulpAPI prefix in api call
ShaiahWren Jun 26, 2023
1e0e260
add spot for queryParams
ShaiahWren Jun 26, 2023
e96c4ba
disable tsc for the time-being
ShaiahWren Jun 26, 2023
1c76994
create a namespace metadata type file
ShaiahWren Jun 29, 2023
26d5db4
add new HubNamespaceResponse and fix type errors
ShaiahWren Jul 10, 2023
d873a30
update Collection type to CollectionVersionSearch
ShaiahWren Jul 11, 2023
73fed34
use CollectionVersionSearch in place of Collection
ShaiahWren Jul 12, 2023
86bc059
reset files to origin
ShaiahWren Jul 12, 2023
a6d550a
use collection search for collections tab
ShaiahWren Jul 13, 2023
63a92a1
fix HubNamespace type
ShaiahWren Jul 13, 2023
e8e8b7a
add collectionversionsearch
ShaiahWren Jul 17, 2023
c4cd163
dealing with tsc errors
ShaiahWren Jul 17, 2023
bd140cf
use ToolBarFilterType for filter
ShaiahWren Jul 25, 2023
01cfea0
use v3/namespaces api for delete method
ShaiahWren Jul 25, 2023
9de23e0
ns metadata tab in ns details
ShaiahWren Jul 27, 2023
8cf081c
add namespace details tab info
ShaiahWren Aug 3, 2023
7076979
namespace name on edit page should be readonly
ShaiahWren Aug 7, 2023
cf0cc82
edit forms for namespaces and sub-namespaces
ShaiahWren Aug 15, 2023
232e646
update sorting and filters
ShaiahWren Aug 15, 2023
c3110ff
remove create button from ns details page
ShaiahWren Aug 15, 2023
a4fedec
fix repo filter
ShaiahWren Aug 16, 2023
72eab49
change ns details edit route name
ShaiahWren Aug 17, 2023
ff89ec9
fix tsc error
ShaiahWren Aug 22, 2023
f646229
fix rebase issues
ShaiahWren Aug 22, 2023
a0af763
fix some eslint errors
ShaiahWren Aug 22, 2023
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
2 changes: 1 addition & 1 deletion frontend/Routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export const RouteObj = {
NamespaceDetails: `${hubRoutePrefix}/namespaces/:id`,
CreateNamespace: `${hubRoutePrefix}/namespaces/create`,
EditNamespace: `${hubRoutePrefix}/namespaces/:id/edit`,

EditNamespaceMetadataDetails: `${hubRoutePrefix}/namespaces/:id/edit-details`,
Approvals: `${hubRoutePrefix}/approvals`,
ApprovalDetails: `${hubRoutePrefix}/approvals/details/:id`,

Expand Down
5 changes: 5 additions & 0 deletions frontend/hub/HubRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { HubDashboard } from './dashboard/Dashboard';
import { ExecutionEnvironments } from './execution-environments/ExecutionEnvironments';
import { NamespaceDetails } from './namespaces/HubNamespaceDetails';
import { CreateHubNamespace, EditHubNamespace } from './namespaces/HubNamespaceForm';
import { EditHubNamespaceMetadata } from './namespaces/HubNamespaceMetadataForm';
import { CreateRemote } from './remotes/RemoteForm';
import { Namespaces } from './namespaces/HubNamespaces';
import { RemoteRegistries } from './remote-registries/RemoteRegistries';
Expand All @@ -34,6 +35,10 @@ export function HubRouter() {
<Route path={RouteObjWithoutPrefix.CreateNamespace} element={<CreateHubNamespace />} />
<Route path={RouteObjWithoutPrefix.EditNamespace} element={<EditHubNamespace />} />
<Route path={RouteObjWithoutPrefix.NamespaceDetails} element={<NamespaceDetails />} />
<Route
path={RouteObjWithoutPrefix.EditNamespaceMetadataDetails}
element={<EditHubNamespaceMetadata />}
/>

<Route path={RouteObjWithoutPrefix.Repositories} element={<Repositories />} />
<Route path={RouteObjWithoutPrefix.Approvals} element={<Approvals />} />
Expand Down
103 changes: 103 additions & 0 deletions frontend/hub/api.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { AutomationServerType } from '../automation-servers/AutomationServer';
import { activeAutomationServer } from '../automation-servers/AutomationServersProvider';

function apiTag(strings: TemplateStringsArray, ...values: string[]) {
if (strings[0]?.[0] !== '/') {
throw new Error('Invalid URL');
}

let url = '';
strings.forEach((fragment, index) => {
url += fragment;
if (index !== strings.length - 1) {
url += encodeURIComponent(`${values.shift() ?? ''}`);
}
});

return url;
}

export function hubAPI(strings: TemplateStringsArray, ...values: string[]) {
let base = process.env.HUB_API_BASE_PATH;
if (!base) {
if (activeAutomationServer?.type === AutomationServerType.Galaxy) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(remember to rebase, this file already exists on main, and the activeAutomationServer logic is obsolete)

base = '/api/galaxy';
} else {
base = '/api/automation-hub';
}
}
return base + apiTag(strings, ...values);
}

export function pulpAPI(strings: TemplateStringsArray, ...values: string[]) {
let base = process.env.HUB_API_BASE_PATH;
if (!base) {
if (activeAutomationServer?.type === AutomationServerType.Galaxy) {
base = '/api/galaxy';
} else {
base = '/api/automation-hub';
}
}
return base + '/pulp/api/v3' + apiTag(strings, ...values);
}

export type QueryParams = {
[key: string]: string | undefined;
};

export function getQueryString(queryParams: QueryParams) {
return Object.entries(queryParams)
.map(([key, value = '']) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
}

const UUIDRegEx = /\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b/i;

export function parsePulpIDFromURL(url: string): string | null {
for (const section of url.split('/')) {
if (section.match(UUIDRegEx)) {
return section;
}
}

return null;
}

// pulp next links currently include full url - with the wrong server
// "http://localhost:5001/api/page/next?what#ever" -> "/api/page/next?what#ever"
// also has to handle hub links (starting with /api/) and undefined
export function serverlessURL(url?: string) {
if (!url || url.startsWith('/')) {
return url;
}

const { pathname, search, hash } = new URL(url);
return `${pathname}${search}${hash}`;
}

export function pulpIdKeyFn(item: { pulp_id: string }) {
return item.pulp_id;
}

export function pulpHrefKeyFn(item: { pulp_href: string }) {
return item.pulp_href;
}

export function nameKeyFn(item: { name: string }) {
return item.name;
}

export function idKeyFn(item: { id: number | string }) {
return item.id;
}

export function collectionKeyFn(item: {
collection_version: { pulp_href: string };
repository: { name: string };
}) {
return item.collection_version.pulp_href + '_' + item.repository.name;
}

export function appendTrailingSlash(url: string) {
return url.endsWith('/') ? url : url + '/';
}
10 changes: 5 additions & 5 deletions frontend/hub/collections/CollectionDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import { StatusCell } from '../../common/Status';
import { useGet } from '../../common/crud/useGet';
import { hubAPI } from '../api/utils';
import { HubItemsResponse } from '../useHubView';
import { CollectionVersionSearch } from './Collection';
import { Collection } from './Collection';
import { useCollectionActions } from './hooks/useCollectionActions';
import { useCollectionColumns } from './hooks/useCollectionColumns';

Expand All @@ -64,18 +64,18 @@ export function CollectionDetails() {
params.namespace || ''
}&repository=${params.repository || ''} }`
);
let collection: CollectionVersionSearch | undefined = undefined;
let collection: Collection | undefined = undefined;
if (data && data.data && data.data.length > 0) {
collection = data.data[0];
}
const itemActions = useCollectionActions(() => void refresh());
return (
<PageLayout>
<PageHeader
title={collection?.collection_version.name}
title={collection?.name}
breadcrumbs={[
{ label: t('Collections'), to: RouteObj.Collections },
{ label: collection?.collection_version.name },
{ label: collection?.name },
]}
headerActions={
<PageActions<CollectionVersionSearch>
Expand Down Expand Up @@ -109,7 +109,7 @@ export function CollectionDetails() {
);
}

function CollectionDetailsTab(props: { collection?: CollectionVersionSearch }) {
function CollectionDetailsTab(props: { collection?: Collection }) {
const { collection } = props;
const tableColumns = useCollectionColumns();
return <PageDetailsFromColumns item={collection} columns={tableColumns} />;
Expand Down
62 changes: 39 additions & 23 deletions frontend/hub/collections/hooks/useCollectionColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,19 @@ import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ITableColumn, TextCell } from '../../../../framework';
import { RouteObj } from '../../../Routes';
import { CollectionVersionSearch } from '../Collection';
import { Collection } from '../Collection';

export function useCollectionColumns(_options?: { disableSort?: boolean; disableLinks?: boolean }) {
const { t } = useTranslation();
return useMemo<ITableColumn<CollectionVersionSearch>[]>(
return useMemo<ITableColumn<Collection>[]>(
() => [
{
header: t('Name'),
value: (collection) => collection.collection_version.name,
value: (collection) => collection.name,
cell: (collection) => (
<TextCell
text={collection.collection_version.name}
to={
RouteObj.CollectionDetails +
'?name=' +
collection.collection_version.name +
'&namespace=' +
collection.collection_version.namespace +
'&repository=' +
collection.repository.name
}
text={collection.name}
to={RouteObj.CollectionDetails.replace(':id', collection.name)}
/>
),
card: 'name',
Expand All @@ -44,48 +36,67 @@ export function useCollectionColumns(_options?: { disableSort?: boolean; disable
{
header: t('Namespace'),
type: 'text',
value: (collection) => collection.collection_version.namespace,
sort: 'namespace',
value: (collection) => collection.namespace.name,
},
{
header: t('Description'),
type: 'description',
value: (collection) => collection.collection_version.description,
value: (collection) => collection.latest_version.metadata.description,
card: 'description',
list: 'description',
},
{
header: t('Modules'),
type: 'count',
value: (collection) =>
collection.collection_version.contents.filter((c) => c.content_type === 'module').length,
collection.latest_version.metadata.contents.filter((c) => c.content_type === 'module')
.length,
},
{
header: t('Roles'),
type: 'count',
value: (collection) =>
collection.latest_version.metadata.contents.filter((c) => c.content_type === 'TODO')
.length,
},
{
header: t('Plugins'),
type: 'count',
value: (collection) =>
collection.latest_version.metadata.contents.filter((c) => c.content_type === 'TODO')
.length,
},
{
header: t('Dependencies'),
type: 'count',
value: (collection) => Object.keys(collection.latest_version.metadata.dependencies).length,
},
{
header: t('Updated'),
type: 'datetime',
value: (collection) => collection.collection_version.pulp_created,
value: (collection) => collection.latest_version.created_at,
card: 'hidden',
list: 'secondary',
},
{
header: t('Version'),
type: 'text',
value: (collection) => collection.collection_version.version,
value: (collection) => collection.latest_version.version,
card: 'hidden',
list: 'secondary',
sort: 'version',
},
{
header: t('Signed state'),
cell: (collection) => {
switch (collection.is_signed) {
case true:
switch (collection.latest_version.sign_state) {
case 'signed':
return (
<Label icon={<CheckCircleIcon />} variant="outline" color="green">
{t('Signed')}
</Label>
);
case false:
case 'unsigned':
return (
<Label icon={<ExclamationTriangleIcon />} variant="outline" color="orange">
{t('Unsigned')}
Expand All @@ -94,7 +105,12 @@ export function useCollectionColumns(_options?: { disableSort?: boolean; disable
}
},
list: 'secondary',
value: (collection) => collection.is_signed,
value: (collection) => collection.latest_version.sign_state,
},
{
header: t('Tags'),
type: 'labels',
value: (collection) => collection.latest_version.metadata.tags.sort(),
},
],
[t]
Expand Down
91 changes: 91 additions & 0 deletions frontend/hub/collections/hooks/useCollectionVersionColumns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Label } from '@patternfly/react-core';
import {
AnsibleTowerIcon,
CheckCircleIcon,
ExclamationTriangleIcon,
} from '@patternfly/react-icons';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ITableColumn, TextCell } from '../../../../framework';
import { RouteObj } from '../../../Routes';
import { CollectionVersionSearch } from '../CollectionVersionSearch';

export function useCollectionVersionColumns() {
const { t } = useTranslation();
return useMemo<ITableColumn<CollectionVersionSearch>[]>(
() => [
{
header: t('Name'),
value: (collection) => collection.collection_version.name,
cell: (collection) => (
<TextCell
text={collection.collection_version.name}
to={RouteObj.CollectionDetails.replace(':id', collection.collection_version.name)}
/>
),
card: 'name',
list: 'name',
icon: () => <AnsibleTowerIcon />,
},
{
header: t('Repository'),
type: 'text',
value: (collection) => collection.repository.name,
},
{
header: t('Namespace'),
type: 'text',
value: (collection) => collection.collection_version.namespace,
},
{
header: t('Description'),
type: 'description',
value: (collection) => collection.collection_version.description,
card: 'description',
list: 'description',
},
{
header: t('Modules'),
type: 'count',
value: (collection) =>
collection.collection_version.contents.filter((c) => c.content_type === 'module').length,
},
{
header: t('Updated'),
type: 'datetime',
value: (collection) => collection.collection_version.pulp_created,
card: 'hidden',
list: 'secondary',
},
{
header: t('Version'),
type: 'text',
value: (collection) => collection.collection_version.version,
card: 'hidden',
list: 'secondary',
},
{
header: t('Signed state'),
cell: (collection) => {
switch (collection.is_signed) {
case true:
return (
<Label icon={<CheckCircleIcon />} variant="outline" color="green">
{t('Signed')}
</Label>
);
case false:
return (
<Label icon={<ExclamationTriangleIcon />} variant="outline" color="orange">
{t('Unsigned')}
</Label>
);
}
},
list: 'secondary',
value: (collection) => collection.is_signed,
},
],
[t]
);
}
Loading