-
Notifications
You must be signed in to change notification settings - Fork 593
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds support for export app in topology
- Loading branch information
1 parent
0d97496
commit d5c5af6
Showing
14 changed files
with
473 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
frontend/packages/topology/src/components/export-app/ExportApplication.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import * as React from 'react'; | ||
import { ToolbarItem, Button, AlertVariant } from '@patternfly/react-core'; | ||
import { useTranslation, Trans } from 'react-i18next'; | ||
import { useAccessReview } from '@console/internal/components/utils'; | ||
import { dateTimeFormatter } from '@console/internal/components/utils/datetime'; | ||
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; | ||
import { | ||
k8sCreate, | ||
k8sKill, | ||
K8sResourceKind, | ||
WatchK8sResource, | ||
} from '@console/internal/module/k8s'; | ||
import { | ||
useFlag, | ||
useIsMobile, | ||
USERSETTINGS_PREFIX, | ||
useToast, | ||
useUserSettings, | ||
} from '@console/shared/src'; | ||
import { ALLOW_EXPORT_APP, EXPORT_CR_NAME } from '../../const'; | ||
import { ExportModel } from '../../models'; | ||
import { getExportAppData } from '../../utils/export-app-utils'; | ||
import exportApplicationModal from './export-app-modal'; | ||
import { exportAppUserSettings } from './types'; | ||
|
||
type ExportApplicationProps = { | ||
namespace: string; | ||
isDisabled: boolean; | ||
}; | ||
|
||
const ExportApplication: React.FC<ExportApplicationProps> = ({ namespace, isDisabled }) => { | ||
const { t } = useTranslation(); | ||
const isMobile = useIsMobile(); | ||
const isExportAppAllowed = useFlag(ALLOW_EXPORT_APP); | ||
const canExportApp = useAccessReview({ | ||
group: ExportModel.apiGroup, | ||
resource: ExportModel.plural, | ||
verb: 'create', | ||
namespace, | ||
}); | ||
const toast = useToast(); | ||
const [exportAppToast, setExportAppToast] = useUserSettings<exportAppUserSettings>( | ||
`${USERSETTINGS_PREFIX}.exportApp`, | ||
{}, | ||
true, | ||
); | ||
|
||
const exportAppResource: WatchK8sResource = React.useMemo( | ||
() => ({ | ||
kind: ExportModel.kind, | ||
name: EXPORT_CR_NAME, | ||
namespace, | ||
namespaced: true, | ||
isList: false, | ||
optional: true, | ||
}), | ||
[namespace], | ||
); | ||
|
||
const [exportRes, exportLoaded, exportLoadErr] = useK8sWatchResource<K8sResourceKind>( | ||
exportAppResource, | ||
); | ||
|
||
const createExportCR = async () => { | ||
// if status is complete then delete export primer CR and create new one or if no cr exists then just create | ||
try { | ||
if (exportLoaded && !exportLoadErr) { | ||
if (exportRes?.status?.completed) await k8sKill(ExportModel, exportRes); | ||
const exportResp = await k8sCreate<K8sResourceKind>( | ||
ExportModel, | ||
getExportAppData(namespace), | ||
); | ||
const key = `${namespace}-${exportResp.metadata.name}`; | ||
const exportAppToastConfig = { | ||
...exportAppToast, | ||
[key]: { | ||
uid: exportResp.metadata.uid, | ||
name: exportResp.metadata.name, | ||
kind: exportResp.kind, | ||
namespace, | ||
}, | ||
}; | ||
setExportAppToast(exportAppToastConfig); | ||
toast.addToast({ | ||
variant: AlertVariant.info, | ||
title: t('topology~Export Application'), | ||
content: ( | ||
<Trans t={t} ns="topology"> | ||
Export of resources in <strong>{{ namespace }}</strong> has started. | ||
</Trans> | ||
), | ||
dismissible: true, | ||
timeout: true, | ||
}); | ||
} | ||
} catch (error) { | ||
toast.addToast({ | ||
variant: AlertVariant.danger, | ||
title: t('topology~Export Application'), | ||
content: ( | ||
<Trans t={t} ns="topology"> | ||
Export of resources in <strong>{{ namespace }}</strong> has failed with error:{' '} | ||
{error.message} | ||
</Trans> | ||
), | ||
dismissible: true, | ||
timeout: true, | ||
}); | ||
} | ||
}; | ||
|
||
const exportAppClickHandle = () => { | ||
// show a modal if export status is in progress | ||
if (exportLoaded && !exportLoadErr && exportRes && exportRes.status?.completed !== true) { | ||
const startTime = dateTimeFormatter.format(new Date(exportRes.metadata.creationTimestamp)); | ||
exportApplicationModal({ namespace, startTime }); | ||
} else { | ||
createExportCR(); | ||
} | ||
}; | ||
|
||
return canExportApp && isExportAppAllowed && !isMobile ? ( | ||
<ToolbarItem> | ||
<Button | ||
variant="secondary" | ||
data-test="export-app-btn" | ||
aria-label={t('topology~Export Application')} | ||
isDisabled={isDisabled} | ||
onClick={exportAppClickHandle} | ||
> | ||
{t('topology~Export Application')} | ||
</Button> | ||
</ToolbarItem> | ||
) : null; | ||
}; | ||
|
||
export default ExportApplication; |
63 changes: 63 additions & 0 deletions
63
frontend/packages/topology/src/components/export-app/__tests__/ExportApplication.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import * as React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
import * as utils from '@console/internal/components/utils'; | ||
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook'; | ||
import * as shared from '@console/shared'; | ||
import ExportApplication from '../ExportApplication'; | ||
import { mockExportData } from './export-data'; | ||
|
||
jest.mock('react-i18next', () => { | ||
const reactI18next = require.requireActual('react-i18next'); | ||
return { | ||
...reactI18next, | ||
useTranslation: () => ({ t: (key) => key }), | ||
}; | ||
}); | ||
|
||
jest.mock('@console/internal/components/utils/k8s-watch-hook', () => ({ | ||
useK8sWatchResource: jest.fn(), | ||
})); | ||
|
||
describe('ExportApplication', () => { | ||
const spyUseAccessReview = jest.spyOn(utils, 'useAccessReview'); | ||
const spyUseFlag = jest.spyOn(shared, 'useFlag'); | ||
const spyUseIsMobile = jest.spyOn(shared, 'useIsMobile'); | ||
const spyUseToast = jest.spyOn(shared, 'useToast'); | ||
const spyUseUserSettings = jest.spyOn(shared, 'useUserSettings'); | ||
|
||
beforeEach(() => { | ||
spyUseToast.mockReturnValue({ addToast: (v) => ({ v }) }); | ||
spyUseUserSettings.mockReturnValue([mockExportData, jest.fn(), false]); | ||
(useK8sWatchResource as jest.Mock).mockReturnValue([{}, true, null]); | ||
}); | ||
afterEach(() => { | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
it('should render export app btn when feature flag is present and user has access export CR and not mobile', () => { | ||
spyUseFlag.mockReturnValue(true); | ||
spyUseAccessReview.mockReturnValue(true); | ||
spyUseIsMobile.mockReturnValue(false); | ||
|
||
const wrapper = shallow(<ExportApplication namespace="my-app" isDisabled={false} />); | ||
expect(wrapper.find('[data-test="export-app-btn"]').exists()).toBe(true); | ||
}); | ||
|
||
it('should not render export app btn when feature flag is present but user do not has access to create export CR and not mobile', () => { | ||
spyUseFlag.mockReturnValue(true); | ||
spyUseAccessReview.mockReturnValue(false); | ||
spyUseIsMobile.mockReturnValue(false); | ||
|
||
const wrapper = shallow(<ExportApplication namespace="my-app" isDisabled={false} />); | ||
expect(wrapper.find('[data-test="export-app-btn"]').exists()).toBe(false); | ||
}); | ||
|
||
it('should not render export app btn when feature flag is present and user has access to create export CR but on mobile', () => { | ||
spyUseFlag.mockReturnValue(true); | ||
spyUseAccessReview.mockReturnValue(true); | ||
spyUseIsMobile.mockReturnValue(true); | ||
|
||
const wrapper = shallow(<ExportApplication namespace="my-app" isDisabled={false} />); | ||
expect(wrapper.find('[data-test="export-app-btn"]').exists()).toBe(false); | ||
}); | ||
}); |
52 changes: 52 additions & 0 deletions
52
frontend/packages/topology/src/components/export-app/__tests__/export-data.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { K8sResourceKind } from '@console/internal/module/k8s'; | ||
|
||
export const mockExportData: K8sResourceKind = { | ||
apiVersion: 'primer.gitops.io/v1alpha1', | ||
kind: 'Export', | ||
metadata: { | ||
creationTimestamp: '2021-08-19T08:12:36Z', | ||
generation: 1, | ||
managedFields: [ | ||
{ | ||
apiVersion: 'primer.gitops.io/v1alpha1', | ||
fieldsType: 'FieldsV1', | ||
fieldsV1: { | ||
'f:spec': { | ||
'.': {}, | ||
'f:method': {}, | ||
}, | ||
}, | ||
manager: 'Mozilla', | ||
operation: 'Update', | ||
time: '2021-08-19T08:12:36Z', | ||
}, | ||
{ | ||
apiVersion: 'primer.gitops.io/v1alpha1', | ||
fieldsType: 'FieldsV1', | ||
fieldsV1: { | ||
'f:status': { | ||
'.': {}, | ||
'f:completed': {}, | ||
'f:route': {}, | ||
}, | ||
}, | ||
manager: 'manager', | ||
operation: 'Update', | ||
subresource: 'status', | ||
time: '2021-08-19T08:14:14Z', | ||
}, | ||
], | ||
name: 'primer', | ||
namespace: 'jai-test', | ||
resourceVersion: '132256', | ||
uid: 'ec61b5d6-5904-4491-b02c-96873e6cdfdf', | ||
}, | ||
spec: { | ||
method: 'download', | ||
}, | ||
status: { | ||
completed: true, | ||
route: | ||
'https://primer-export-primer-jai-test.apps.jakumar-2021-08-18-144658.devcluster.openshift.com/ec61b5d6-5904-4491-b02c-96873e6cdfdf.zip', | ||
}, | ||
}; |
Oops, something went wrong.