зеркало из https://github.com/microsoft/AzureTRE.git
UI - Add Upgrade Button to Resources (#3387)
* add upgrade button to resources that can be upgraded (non major upgrades only) * update changelog * update ui version * fix cr review comments * Update ui/app/src/components/shared/ConfirmUpgradeResource.tsx Co-authored-by: James Griffin <me@JamesGriff.in> --------- Co-authored-by: James Griffin <me@JamesGriff.in>
This commit is contained in:
Родитель
b1544e805a
Коммит
9167c956be
|
@ -4,6 +4,7 @@
|
|||
A migration for OperationSteps in Operation objects was added ([#3358](https://github.com/microsoft/AzureTRE/pull/3358)).
|
||||
|
||||
FEATURES:
|
||||
* (UI) Added upgrade button to resources that have pending template upgrades ([#3387](https://github.com/microsoft/AzureTRE/pull/3387))
|
||||
|
||||
ENHANCEMENTS:
|
||||
* Added 'availableUpgrades' field to Resources in GET/GET all Resources endpoints. The field indicates whether there are template versions that a resource can be upgraded to [#3234](https://github.com/microsoft/AzureTRE/pull/3234)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tre-ui",
|
||||
"version": "0.4.2",
|
||||
"version": "0.5.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@azure/msal-browser": "^2.33.0",
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
import { Dialog, DialogFooter, PrimaryButton, DialogType, Spinner, Dropdown, MessageBar, MessageBarType, DropdownMenuItemType, IDropdownOption, Icon, Stack, Label, IconButton, IDropdownProps } from '@fluentui/react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { AvailableUpgrade, Resource } from '../../models/resource';
|
||||
import { HttpMethod, ResultType, useAuthApiCall } from '../../hooks/useAuthApiCall';
|
||||
import { WorkspaceContext } from '../../contexts/WorkspaceContext';
|
||||
import { ResourceType } from '../../models/resourceType';
|
||||
import { APIError } from '../../models/exceptions';
|
||||
import { LoadingState } from '../../models/loadingState';
|
||||
import { ExceptionLayout } from './ExceptionLayout';
|
||||
import { useAppDispatch } from '../../hooks/customReduxHooks';
|
||||
import { addUpdateOperation } from '../shared/notifications/operationsSlice';
|
||||
|
||||
interface ConfirmUpgradeProps {
|
||||
resource: Resource,
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
export const ConfirmUpgradeResource: React.FunctionComponent<ConfirmUpgradeProps> = (props: ConfirmUpgradeProps) => {
|
||||
const apiCall = useAuthApiCall();
|
||||
const [selectedVersion, setSelectedVersion] = useState("")
|
||||
const [apiError, setApiError] = useState({} as APIError);
|
||||
const [requestLoadingState, setRequestLoadingState] = useState(LoadingState.Ok);
|
||||
const workspaceCtx = useContext(WorkspaceContext);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const upgradeProps = {
|
||||
type: DialogType.normal,
|
||||
title: `Upgrade Template Version?`,
|
||||
closeButtonAriaLabel: 'Close',
|
||||
subText: `Are you sure you want upgrade the template version of ${props.resource.properties.display_name} from version ${props.resource.templateVersion}?`,
|
||||
};
|
||||
|
||||
const dialogStyles = { main: { maxWidth: 450 } };
|
||||
const modalProps = {
|
||||
titleAriaId: 'labelId',
|
||||
subtitleAriaId: 'subTextId',
|
||||
isBlocking: true,
|
||||
styles: dialogStyles
|
||||
};
|
||||
|
||||
const wsAuth = (props.resource.resourceType === ResourceType.WorkspaceService || props.resource.resourceType === ResourceType.UserResource);
|
||||
|
||||
const upgradeCall = async () => {
|
||||
setRequestLoadingState(LoadingState.Loading);
|
||||
try {
|
||||
let body = { templateVersion: selectedVersion }
|
||||
let op = await apiCall(props.resource.resourcePath,
|
||||
HttpMethod.Patch,
|
||||
wsAuth ? workspaceCtx.workspaceApplicationIdURI : undefined,
|
||||
body,
|
||||
ResultType.JSON,
|
||||
undefined,
|
||||
undefined,
|
||||
props.resource._etag);
|
||||
dispatch(addUpdateOperation(op.operation));
|
||||
props.onDismiss();
|
||||
} catch (err: any) {
|
||||
err.userMessage = 'Failed to upgrade resource';
|
||||
setApiError(err);
|
||||
setRequestLoadingState(LoadingState.Error);
|
||||
}
|
||||
}
|
||||
|
||||
const onRenderOption = (option: any): JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
{option.data && option.data.icon && (
|
||||
<Icon style={{ marginRight: '8px' }} iconName={option.data.icon} aria-hidden="true" title={option.data.icon} />
|
||||
)}
|
||||
<span>{option.text}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const convertToDropDownOptions = (upgrade: Array<AvailableUpgrade>) => {
|
||||
return upgrade.map(upgrade => ({ "key": upgrade.version, "text": upgrade.version, data: { icon: upgrade.forceUpdateRequired ? 'Warning' : '' } }))
|
||||
}
|
||||
|
||||
const getDropdownOptions = () => {
|
||||
const options = []
|
||||
const nonMajorUpgrades = props.resource.availableUpgrades.filter(upgrade => !upgrade.forceUpdateRequired)
|
||||
options.push(...convertToDropDownOptions(nonMajorUpgrades))
|
||||
return options;
|
||||
}
|
||||
|
||||
return (<>
|
||||
<Dialog
|
||||
hidden={false}
|
||||
onDismiss={() => props.onDismiss()}
|
||||
dialogContentProps={upgradeProps}
|
||||
modalProps={modalProps}
|
||||
>
|
||||
{
|
||||
requestLoadingState === LoadingState.Ok &&
|
||||
<>
|
||||
<MessageBar messageBarType={MessageBarType.warning} >Upgrading the template version is irreversible.</MessageBar>
|
||||
<DialogFooter>
|
||||
<Dropdown
|
||||
placeholder='Select Version'
|
||||
options={getDropdownOptions()}
|
||||
onRenderOption={onRenderOption}
|
||||
styles={{ dropdown: { width: 125 } }}
|
||||
onChange={(event, option) => { option && setSelectedVersion(option.text); }}
|
||||
selectedKey={selectedVersion}
|
||||
/>
|
||||
<PrimaryButton primaryDisabled={!selectedVersion} text="Upgrade" onClick={() => upgradeCall()} />
|
||||
</DialogFooter>
|
||||
</>
|
||||
}
|
||||
{
|
||||
requestLoadingState === LoadingState.Loading &&
|
||||
<Spinner label="Sending request..." ariaLive="assertive" labelPosition="right" />
|
||||
}
|
||||
{
|
||||
requestLoadingState === LoadingState.Error &&
|
||||
<ExceptionLayout e={apiError} />
|
||||
}
|
||||
</Dialog>
|
||||
</>);
|
||||
};
|
|
@ -18,6 +18,7 @@ import { actionsDisabledStates } from '../../models/operation';
|
|||
import { AppRolesContext } from '../../contexts/AppRolesContext';
|
||||
import { useAppDispatch } from '../../hooks/customReduxHooks';
|
||||
import { addUpdateOperation } from '../shared/notifications/operationsSlice';
|
||||
import { ConfirmUpgradeResource } from './ConfirmUpgradeResource';
|
||||
|
||||
interface ResourceContextMenuProps {
|
||||
resource: Resource,
|
||||
|
@ -30,6 +31,7 @@ export const ResourceContextMenu: React.FunctionComponent<ResourceContextMenuPro
|
|||
const workspaceCtx = useContext(WorkspaceContext);
|
||||
const [showDisable, setShowDisable] = useState(false);
|
||||
const [showDelete, setShowDelete] = useState(false);
|
||||
const [showUpgrade, setShowUpgrade] = useState(false);
|
||||
const [resourceTemplate, setResourceTemplate] = useState({} as ResourceTemplate);
|
||||
const createFormCtx = useContext(CreateUpdateResourceContext);
|
||||
const [parentResource, setParentResource] = useState({} as WorkspaceService | Workspace);
|
||||
|
@ -182,6 +184,19 @@ export const ResourceContextMenu: React.FunctionComponent<ResourceContextMenuPro
|
|||
});
|
||||
}
|
||||
|
||||
// add 'upgrade' button if we have available template upgrades
|
||||
const nonMajorUpgrades = props.resource.availableUpgrades?.filter(upgrade => !upgrade.forceUpdateRequired)
|
||||
if (nonMajorUpgrades.length > 0) {
|
||||
menuItems.push({
|
||||
key: 'upgrade',
|
||||
text: 'Upgrade',
|
||||
title: 'Upgrade this resource template version',
|
||||
iconProps: { iconName: 'Refresh' },
|
||||
onClick: () => setShowUpgrade(true),
|
||||
disabled: (props.componentAction === ComponentAction.Lock)
|
||||
})
|
||||
}
|
||||
|
||||
const menuProps: IContextualMenuProps = {
|
||||
shouldFocusOnMount: true,
|
||||
items: menuItems
|
||||
|
@ -206,6 +221,10 @@ export const ResourceContextMenu: React.FunctionComponent<ResourceContextMenuPro
|
|||
showDelete &&
|
||||
<ConfirmDeleteResource onDismiss={() => setShowDelete(false)} resource={props.resource} />
|
||||
}
|
||||
{
|
||||
showUpgrade &&
|
||||
<ConfirmUpgradeResource onDismiss={() => setShowUpgrade(false)} resource={props.resource} />
|
||||
}
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface Resource {
|
|||
resourceType: ResourceType
|
||||
templateName: string,
|
||||
templateVersion: string,
|
||||
availableUpgrades: Array<AvailableUpgrade>,
|
||||
deploymentStatus: string,
|
||||
updatedWhen: number,
|
||||
user: User,
|
||||
|
@ -30,6 +31,11 @@ export interface HistoryItem {
|
|||
templateVersion: string
|
||||
}
|
||||
|
||||
export interface AvailableUpgrade {
|
||||
version: string,
|
||||
forceUpdateRequired : boolean
|
||||
}
|
||||
|
||||
export enum ComponentAction {
|
||||
None,
|
||||
Reload,
|
||||
|
|
Загрузка…
Ссылка в новой задаче