-
Notifications
You must be signed in to change notification settings - Fork 15
WIP Prototype multicluster sdk #169
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
base: main
Are you sure you want to change the base?
Changes from all commits
6d09f77
0e777e0
68cb17a
586b924
039a7aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'; |
| 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); | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 This will be modified when there is a new minimum version of ACM required for new features in the SDK. Checking via |
||
| } | ||
| }, [isFleetAvailable, setFeatureFlag]); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 && ( | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On some apps created in ACM, I'm finding |
||
| <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> | ||
|
|
||
|
|
@@ -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 | ||
|
|
||
| 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'; | ||
|
|
@@ -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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
| }); | ||
|
|
||
| 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, | ||
|
|
@@ -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'; | ||
|
|
@@ -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', | ||
|
|
@@ -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[]; | ||
|
|
@@ -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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Normally I would recommend just converting to a 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 | ||
| ? [ | ||
| { | ||
|
|
@@ -371,15 +406,23 @@ const useApplicationRowsDV = (applicationsList, namespace): DataViewTr[] => { | |
| id: app?.status?.sync?.revision, | ||
| cell: ( | ||
| <> | ||
| {sources[0].targetRevision ? sources[0].targetRevision : 'HEAD'} | ||
| {!(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'} | ||
| | ||
| {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'} | ||
| /> | ||
| )} | ||
| </> | ||
| ), | ||
| }, | ||
|
|
@@ -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[] = [ | ||
| { | ||
|
|
@@ -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), | ||
| }, | ||
| }, | ||
| ] | ||
|
|
||
| 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; | ||
|
|
@@ -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} />} | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| </> | ||
| ); | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
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.