Skip to content
Draft
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
18 changes: 18 additions & 0 deletions console-extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
},
"type": "console.flag"
},
{
"properties": { "handler": { "$codeRef": "gitopsFlags.useGitOpsMulticlusterFlag" } },
"type": "console.flag/hookProvider"
},
{
"type": "console.navigation/href",
"properties": {
Expand Down Expand Up @@ -460,6 +464,20 @@
}
}
},
{
"flags": {
"required": ["GITOPS_MULTICLUSTER"]
},
"properties": {
"component": {
"$codeRef": "ApplicationDetails"
},
"path": [
"/k8s/cluster/:cluster/ns/:ns/argoproj.io~v1alpha1~Application/:name"
]
},
"type": "console.page/route"
Comment on lines +476 to +479
Copy link
Author

Choose a reason for hiding this comment

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

Because multicluster support is not native to OCP, you need to have your own route defined for multicluster resources.

},
{
"type": "console.page/resource/details",
"flags": {
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@
"@patternfly/quickstarts": "^6.2.0",
"@patternfly/react-charts": "^8.2.0",
"@patternfly/react-core": "^6.2.0",
"@patternfly/react-data-view": "^6.2.0",
"@patternfly/react-icons": "^6.2.0",
"@patternfly/react-table": "^6.2.0",
"@patternfly/react-data-view": "^6.2.0",
"@patternfly/react-tokens": "6.2.0",
"@patternfly/react-topology": "^6.2.0",
"@types/classnames": "^2.3.1",
"@types/git-url-parse": "^9.0.0",
"@types/node": "^17.0.21",
"@types/react": "17.0.89",
"@types/react-redux": "7.1.34",
"@types/webpack-dev-server": "^4.7.2",
"@types/semver": "7.7.1",
"@types/webpack-dev-server": "^4.7.2",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"copy-webpack-plugin": "12.0.2",
Expand All @@ -51,13 +51,13 @@
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"file-loader": "6.2.0",
"http-server": "0.12.x",
"i18next": "^19.8.3",
"i18next-parser": "^3.3.0",
"lodash-es": "^4.17.21",
"marked": "15.0.12",
"prettier": "^2.6.0",
"prop-types": "15.7.x",
"http-server": "0.12.x",
"marked": "15.0.12",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-i18next": "^11.7.3",
Expand Down Expand Up @@ -101,6 +101,7 @@
}
},
"dependencies": {
"@stolostron/multicluster-sdk": "^0.10.1",
"classnames": "^2.3.2",
"git-url-parse": "^13.1.0",
"react": "17.0.2",
Expand Down
1 change: 1 addition & 0 deletions src/components/utils/flags/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { detectOpenShiftVersion } from './detectOpenShiftVersion';
export { enableGitOpsDynamicFlag } from './enableGitOpsDynamicFlag';
export { useGitOpsMulticlusterFlag } from './useGitOpsMulticlusterFlag';
16 changes: 16 additions & 0 deletions src/components/utils/flags/useGitOpsMulticlusterFlag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect } from 'react';

import { SetFeatureFlag } from '@openshift-console/dynamic-plugin-sdk';
import { useIsFleetAvailable } from '@stolostron/multicluster-sdk';

import { FLAG_GITOPS_MULTICLUSTER } from '../../../const';

export const useGitOpsMulticlusterFlag = (setFeatureFlag: SetFeatureFlag) => {
const isFleetAvailable = useIsFleetAvailable();

useEffect(() => {
if (isFleetAvailable) {
setFeatureFlag(FLAG_GITOPS_MULTICLUSTER, isFleetAvailable);
Copy link
Author

Choose a reason for hiding this comment

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

Since your console-extensions.json file is JSON and not TypeScript, you can call our hook to check if multicluster SDK is available. If it was in TypeScript, you could use the REQUIRED_PROVIDER_FLAG constant directly. See https://www.npmjs.com/package/@stolostron/multicluster-sdk#gear-required_provider_flag

This will be modified when there is a new minimum version of ACM required for new features in the SDK. Checking via useIsFleetAvailable will be checking for the same flag, so this is a valid way to do it as well.

}
}, [isFleetAvailable, setFeatureFlag]);
};
2 changes: 2 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export const fetchDataFrequency = 30;
export const FLAG_GITOPS_DYNAMIC = 'GITOPS_DYNAMIC';

export const FLAG_GITOPS_ENABLE_TOPOLOGY = 'GITOPS_ENABLE_TOPOLOGY';

export const FLAG_GITOPS_MULTICLUSTER = 'GITOPS_MULTICLUSTER';
32 changes: 19 additions & 13 deletions src/gitops/components/application/ApplicationDetailsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,18 +217,22 @@ const ApplicationDetailsTab: React.FC<ApplicationDetailsTabProps> = ({ obj }) =>
<FlexItem>
<SyncStatus status={obj.status?.sync?.status || ''} />
</FlexItem>
<FlexItem>
<PfLabel>
<Revision
revision={revisions[0] || ''}
repoURL={sources[0].repoURL}
helm={obj.status?.sourceType == 'Helm' && sources[0].chart ? true : false}
revisionExtra={
revisions.length > 1 && ' and ' + (revisions.length - 1) + ' more'
}
/>
</PfLabel>
</FlexItem>
{sources && sources.length && revisions && revisions.length && (
Copy link
Author

Choose a reason for hiding this comment

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

On some apps created in ACM, I'm finding sources is not always defined. And from ACM search, we will not have this information currently.

<FlexItem>
<PfLabel>
<Revision
revision={revisions[0] || ''}
repoURL={sources[0].repoURL}
helm={
obj.status?.sourceType == 'Helm' && sources[0].chart ? true : false
}
revisionExtra={
revisions.length > 1 && ' and ' + (revisions.length - 1) + ' more'
}
/>
</PfLabel>
</FlexItem>
)}
</Flex>
</DetailsDescriptionGroup>

Expand All @@ -254,7 +258,9 @@ const ApplicationDetailsTab: React.FC<ApplicationDetailsTabProps> = ({ obj }) =>
title={t('Target Revision')}
help={t('The specified revision for the Application.')}
>
{sources[0].targetRevision ? sources[0].targetRevision : 'HEAD'}
{sources && sources.length && sources[0].targetRevision
? sources[0].targetRevision
: 'HEAD'}
</DetailsDescriptionGroup>

<DetailsDescriptionGroup
Expand Down
25 changes: 15 additions & 10 deletions src/gitops/components/application/ApplicationNavPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as React from 'react';
import { useParams } from 'react-router-dom-v5-compat';

import { useApplicationActionsProvider } from '@gitops/hooks/useApplicationActionsProvider';
import { ApplicationKind, ApplicationModel } from '@gitops/models/ApplicationModel';
import { useGitOpsTranslation } from '@gitops/utils/hooks/useGitOpsTranslation';
import { HorizontalNav, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk';
import { HorizontalNav } from '@openshift-console/dynamic-plugin-sdk';
import { Bullseye, Spinner } from '@patternfly/react-core';
import { useFleetK8sWatchResource } from '@stolostron/multicluster-sdk';

import DetailsPageHeader from '../shared/DetailsPageHeader/DetailsPageHeader';
import EventsTab from '../shared/EventsTab/EventsTab';
Expand All @@ -16,21 +18,24 @@ import ApplicationResourcesTab from './ApplicationResourcesTab';
import ApplicationSourcesTab from './ApplicationSourcesTab';
import ApplicationSyncStatusTab from './ApplicationSyncStatusTab';

type ApplicationPageProps = {
name: string;
namespace: string;
kind: string;
};

const ApplicationNavPage: React.FC<ApplicationPageProps> = ({ name, namespace, kind }) => {
const ApplicationNavPage: React.FC = () => {
const { t } = useGitOpsTranslation();
const [application, loaded] = useK8sWatchResource<ApplicationKind>({
const {
cluster,
name,
ns: namespace,
} = useParams<{
cluster?: string;
name: string;
ns: string;
}>();
Comment on lines +23 to +31
Copy link
Author

Choose a reason for hiding this comment

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

Because for the multicluster route it is not behaving as a "console.page/resource/details" extension, the details will not be passed as propped. Getting them from URL params in both single and multicluster case.

const [application, loaded] = useFleetK8sWatchResource<ApplicationKind>({
groupVersionKind: {
group: 'argoproj.io',
kind: 'Application',
version: 'v1alpha1',
},
kind,
cluster,
name,
namespace,
});
Expand Down
102 changes: 79 additions & 23 deletions src/gitops/components/shared/ApplicationList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom-v5-compat';
import DevPreviewBadge from 'src/components/import/badges/DevPreviewBadge';

import { useMulticlusterK8sWatchResource } from '@gitops/hooks/useMuticlusterK8sWatchResource';
import {
Action,
K8sResourceCommon,
Expand All @@ -11,14 +13,18 @@ import {
ListPageHeader,
ResourceLink,
RowFilter,
useK8sWatchResource,
useListPageFilter,
} from '@openshift-console/dynamic-plugin-sdk';
import { ErrorState } from '@patternfly/react-component-groups';
import { EmptyState, EmptyStateBody, Flex, FlexItem, Spinner } from '@patternfly/react-core';
import { DataViewTh, DataViewTr } from '@patternfly/react-data-view/dist/esm/DataViewTable';
import { CubesIcon } from '@patternfly/react-icons';
import { Tbody, Td, ThProps, Tr } from '@patternfly/react-table';
import {
FleetResourceLink,
useHubClusterName,
useIsFleetAvailable,
} from '@stolostron/multicluster-sdk';

import { useApplicationActionsProvider } from '../..//hooks/useApplicationActionsProvider';
import RevisionFragment from '../..//Revision/Revision';
Expand Down Expand Up @@ -83,7 +89,8 @@ const ApplicationList: React.FC<ApplicationProps> = ({
if (listAllNamespaces) {
namespace = null;
}
const [applications, loaded, loadError] = useK8sWatchResource<K8sResourceCommon[]>({

const [applications, loaded, loadError] = useMulticlusterK8sWatchResource<K8sResourceCommon[]>({
isList: true,
groupVersionKind: {
group: 'argoproj.io',
Expand Down Expand Up @@ -305,6 +312,8 @@ const ApplicationActionsCell: React.FC<{ app: ApplicationKind }> = ({ app }) =>
};

const useApplicationRowsDV = (applicationsList, namespace): DataViewTr[] => {
const isFleetAvailable = useIsFleetAvailable();
const [hubClusterName] = useHubClusterName();
const rows: DataViewTr[] = [];
applicationsList.forEach((app, index) => {
let sources: ApplicationSource[];
Expand All @@ -324,21 +333,47 @@ const useApplicationRowsDV = (applicationsList, namespace): DataViewTr[] => {
{
cell: (
<div>
<ResourceLink
groupVersionKind={modelToGroupVersionKind(ApplicationModel)}
name={app.metadata.name}
namespace={app.metadata.namespace}
inline={true}
>
<span className="pf-u-pl-sm">
{isApplicationRefreshing(app) && <Spinner size="sm" />}
</span>
</ResourceLink>
{app.cluster !== hubClusterName ? (
<Link
to={`/k8s/cluster/${app.cluster}/ns/${app.metadata.namespace}/argoproj.io~v1alpha1~Application/${app.metadata.name}`}
>
{app.metadata.name}
</Link>
) : (
<ResourceLink
groupVersionKind={modelToGroupVersionKind(ApplicationModel)}
name={app.metadata.name}
namespace={app.metadata.namespace}
// cluster={app.cluster}
inline={true}
>
<span className="pf-u-pl-sm">
{isApplicationRefreshing(app) && <Spinner size="sm" />}
</span>
</ResourceLink>
)}
Comment on lines +336 to +354
Copy link
Author

Choose a reason for hiding this comment

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

Normally I would recommend just converting to a <FleetResourceLink> here, but the problem is that ACM currently considers itself to be the plugin responsible for displaying Argo apps. See https://github.com/stolostron/console/blob/main/frontend/plugins/acm/console-extensions.ts#L242

That extension allows plugins to declare that they have a route for displaying a given resource kind.

</div>
),
id: app.metadata?.name,
dataLabel: 'Name',
},
...(isFleetAvailable
? [
{
cell: (
<FleetResourceLink
groupVersionKind={{
kind: 'ManagedCluster',
group: 'cluster.open-cluster-management.io',
version: 'v1',
}}
name={app.cluster}
/>
),
dataLabel: 'Cluster',
},
]
: []),
...(!namespace
? [
{
Expand Down Expand Up @@ -371,15 +406,23 @@ const useApplicationRowsDV = (applicationsList, namespace): DataViewTr[] => {
id: app?.status?.sync?.revision,
cell: (
<>
{sources[0].targetRevision ? sources[0].targetRevision : 'HEAD'}&nbsp;
{!(app.status?.sourceType == 'Helm' && sources[0].chart) && (
<RevisionFragment
revision={revisions[0] || ''}
repoURL={sources[0].repoURL}
helm={app.status?.sourceType == 'Helm' && sources[0].chart ? true : false}
revisionExtra={revisions.length > 1 && ' and ' + (revisions.length - 1) + ' more'}
/>
)}
{/* Extra guard code added here - ACM search will not have all of this information */}
{sources && !!sources.length && sources[0].targetRevision
? sources[0].targetRevision
: 'HEAD'}
&nbsp;
{sources &&
!!sources.length &&
!(app.status?.sourceType == 'Helm' && sources[0].chart) &&
revisions &&
!!revisions.length && (
<RevisionFragment
revision={revisions[0] || ''}
repoURL={sources[0].repoURL}
helm={app.status?.sourceType == 'Helm' && sources[0].chart ? true : false}
revisionExtra={revisions.length > 1 && ' and ' + (revisions.length - 1) + ' more'}
/>
)}
</>
),
},
Expand All @@ -406,7 +449,8 @@ const useColumnsDV = (
namespace: string,
getSortParams: (columnIndex: number) => ThProps['sort'],
): DataViewTh[] => {
const i: number = namespace ? 0 : 1;
const isFleetAvailable = useIsFleetAvailable();
const i: number = (namespace ? 0 : 1) + (isFleetAvailable ? 1 : 0);
const { t } = useTranslation('plugin__gitops-plugin');
const columns: DataViewTh[] = [
{
Expand All @@ -417,14 +461,26 @@ const useColumnsDV = (
sort: getSortParams(0),
},
},
...(isFleetAvailable
? [
{
cell: t('Cluster'),
props: {
'aria-label': 'cluster',
className: 'pf-m-width-15',
sort: getSortParams(1),
},
},
]
: []),
...(!namespace
? [
{
cell: t('Namespace'),
props: {
'aria-label': 'namespace',
className: 'pf-m-width-15',
sort: getSortParams(1),
sort: getSortParams(isFleetAvailable ? 2 : 1),
},
},
]
Expand Down
5 changes: 3 additions & 2 deletions src/gitops/components/shared/EventsTab/EventsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react';
import { RouteComponentProps } from 'react-router';

import { K8sResourceCommon, ResourceEventStream } from '@openshift-console/dynamic-plugin-sdk';
import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk';
import { PageSection, Title } from '@patternfly/react-core';
import { FleetResourceEventStream } from '@stolostron/multicluster-sdk';

type EventsTabProps = RouteComponentProps<{
ns: string;
Expand All @@ -17,7 +18,7 @@ const EventsTab: React.FC<EventsTabProps> = ({ obj }) => {
<PageSection>
<Title headingLevel="h2">{obj.kind} events</Title>
</PageSection>
{obj && <ResourceEventStream resource={obj} />}
{obj && <FleetResourceEventStream resource={obj} />}
Copy link
Author

Choose a reason for hiding this comment

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

Drop-in replacement that will work over cluster-proxy if obj.cluster is defined.

</>
);
};
Expand Down
Loading