-
Notifications
You must be signed in to change notification settings - Fork 592
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
8c3bf59
commit 10234fa
Showing
14 changed files
with
475 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
126 changes: 126 additions & 0 deletions
126
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,126 @@ | ||
import * as React from 'react'; | ||
import { ToolbarItem, Button, AlertVariant } from '@patternfly/react-core'; | ||
import * as _ from 'lodash'; | ||
import { useTranslation, Trans } from 'react-i18next'; | ||
import { useAccessReview } from '@console/internal/components/utils'; | ||
import { dateTimeFormatter } from '@console/internal/components/utils/datetime'; | ||
import { k8sCreate, k8sGet, k8sKill, K8sResourceKind } 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 [isCreating, setIsCreating] = React.useState<boolean>(false); | ||
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 createExportCR = async () => { | ||
try { | ||
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, | ||
}, | ||
}; | ||
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, | ||
}); | ||
setExportAppToast(exportAppToastConfig); | ||
setIsCreating(false); | ||
} catch (error) { | ||
setIsCreating(false); | ||
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 = async () => { | ||
try { | ||
setIsCreating(true); | ||
const exportRes = await k8sGet(ExportModel, EXPORT_CR_NAME, namespace); | ||
if (exportRes && exportRes.status?.completed !== true) { | ||
const startTime = dateTimeFormatter.format(new Date(exportRes.metadata.creationTimestamp)); | ||
exportApplicationModal({ namespace, startTime }); | ||
} else if (exportRes && exportRes.status?.completed) { | ||
await k8sKill(ExportModel, exportRes); | ||
const exportAppToastConfig = _.omit(exportAppToast, `${namespace}-${EXPORT_CR_NAME}`); | ||
setExportAppToast(exportAppToastConfig); | ||
createExportCR(); | ||
} | ||
} catch { | ||
if (isCreating) { | ||
exportApplicationModal({ namespace }); | ||
return; | ||
} | ||
createExportCR(); | ||
} | ||
}; | ||
|
||
const showExportAppBtn = canExportApp && isExportAppAllowed && !isMobile; | ||
|
||
return showExportAppBtn ? ( | ||
<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; |
75 changes: 75 additions & 0 deletions
75
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,75 @@ | ||
import * as React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
import * as utils from '@console/internal/components/utils'; | ||
import * as k8s from '@console/internal/module/k8s'; | ||
import * as shared from '@console/shared'; | ||
import { EXPORT_CR_NAME } from '../../../const'; | ||
import { ExportModel } from '../../../models'; | ||
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 }), | ||
}; | ||
}); | ||
|
||
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([{}, jest.fn(), false]); | ||
}); | ||
|
||
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); | ||
}); | ||
|
||
it('should call k8sGet with correct data on click of exportAppBtn', () => { | ||
const spyk8sGet = jest.spyOn(k8s, 'k8sGet'); | ||
spyk8sGet.mockReturnValueOnce(Promise.resolve(mockExportData)); | ||
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); | ||
wrapper.find('[data-test="export-app-btn"]').simulate('click'); | ||
expect(spyk8sGet).toHaveBeenCalledTimes(1); | ||
expect(spyk8sGet).toHaveBeenCalledWith(ExportModel, EXPORT_CR_NAME, 'my-app'); | ||
}); | ||
}); |
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.