Skip to content
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

My Operations + UI Stability improvements #2530

Merged
merged 20 commits into from
Aug 31, 2022
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ENHANCEMENTS:
* Gitea shared service support app-service standard SKUs ([#2523](https://github.com/microsoft/AzureTRE/pull/2523))
* Keyvault diagnostic settings in base workspace ([#2521](https://github.com/microsoft/AzureTRE/pull/2521))
* Airlock requests contain a field with information about the files that were submitted ([#2504](https://github.com/microsoft/AzureTRE/pull/2504))
* UI - Operations and notifications stability improvements ([[#2530](https://github.com/microsoft/AzureTRE/pull/2530)])

BUG FIXES:

Expand Down
2 changes: 1 addition & 1 deletion api_app/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.4.24"
__version__ = "0.4.25"
3 changes: 2 additions & 1 deletion api_app/api/routes/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from api.dependencies.database import get_repository
from db.repositories.workspaces import WorkspaceRepository
from api.routes import health, workspaces, workspace_templates, workspace_service_templates, user_resource_templates, \
shared_services, shared_service_templates, migrations, costs, airlock
shared_services, shared_service_templates, migrations, costs, airlock, operations
from core import config

core_tags_metadata = [
Expand Down Expand Up @@ -38,6 +38,7 @@
core_router.include_router(user_resource_templates.user_resource_templates_core_router, tags=["user resource templates"])
core_router.include_router(shared_service_templates.shared_service_templates_core_router, tags=["shared service templates"])
core_router.include_router(shared_services.shared_services_router, tags=["shared services"])
core_router.include_router(operations.operations_router, tags=["operations"])
core_router.include_router(workspaces.workspaces_core_router, tags=["workspaces"])
core_router.include_router(workspaces.workspaces_shared_router, tags=["workspaces"])
core_router.include_router(migrations.migrations_core_router, tags=["migrations"])
Expand Down
16 changes: 16 additions & 0 deletions api_app/api/routes/operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from fastapi import APIRouter, Depends

from db.repositories.operations import OperationRepository
from api.dependencies.database import get_repository
from models.schemas.operation import OperationInList
from resources import strings
from services.authentication import get_current_tre_user_or_tre_admin


operations_router = APIRouter(dependencies=[Depends(get_current_tre_user_or_tre_admin)])


@operations_router.get("/operations", response_model=OperationInList, name=strings.API_GET_MY_OPERATIONS)
async def get_my_operations(user=Depends(get_current_tre_user_or_tre_admin), operations_repo=Depends(get_repository(OperationRepository))) -> OperationInList:
operations = operations_repo.get_my_operations(user_id=user.id)
return OperationInList(operations=operations)
5 changes: 5 additions & 0 deletions api_app/db/repositories/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ def get_operation_by_id(self, operation_id: str) -> Operation:
raise EntityDoesNotExist
return parse_obj_as(Operation, operation[0])

def get_my_operations(self, user_id: str) -> List[Operation]:
query = self.operations_query() + f' c.user.id = "{user_id}" AND c.status IN ("{Status.AwaitingAction}", "{Status.InvokingAction}", "{Status.AwaitingDeployment}", "{Status.Deploying}", "{Status.AwaitingDeletion}", "{Status.Deleting}", "{Status.AwaitingUpdate}", "{Status.Updating}", "{Status.PipelineRunning}") ORDER BY c.createdWhen ASC'
damoodamoo marked this conversation as resolved.
Show resolved Hide resolved
operations = self.query(query=query)
return parse_obj_as(List[Operation], operations)

def get_operations_by_resource_id(self, resource_id: str) -> List[Operation]:
query = self.operations_query() + f' c.resourceId = "{resource_id}"'
operations = self.query(query=query)
Expand Down
1 change: 1 addition & 0 deletions api_app/resources/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
API_GET_HEALTH_STATUS = "Get health status"
API_MIGRATE_DATABASE = "Migrate documents in the database"

API_GET_MY_OPERATIONS = "Get Operations that the current user has initiated"
API_GET_ALL_WORKSPACES = "Get all workspaces"
API_GET_WORKSPACE_BY_ID = "Get workspace by Id"
API_CREATE_WORKSPACE = "Create a workspace"
Expand Down
37 changes: 16 additions & 21 deletions ui/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import { Workspace } from './models/workspace';
import { AppRolesContext } from './contexts/AppRolesContext';
import { WorkspaceContext } from './contexts/WorkspaceContext';
import { GenericErrorBoundary } from './components/shared/GenericErrorBoundary';
import { NotificationsContext } from './contexts/NotificationsContext';
import { Operation } from './models/operation';
import { ResourceUpdate } from './models/resource';
import { OperationsContext } from './contexts/OperationsContext';
import { completedStates, Operation } from './models/operation';
import { HttpMethod, ResultType, useAuthApiCall } from './hooks/useAuthApiCall';
import { ApiEndpoint } from './models/apiEndpoints';
import { CreateUpdateResource } from './components/shared/create-update-resource/CreateUpdateResource';
Expand All @@ -26,7 +25,6 @@ export const App: React.FunctionComponent = () => {
const [selectedWorkspace, setSelectedWorkspace] = useState({} as Workspace);
const [workspaceRoles, setWorkspaceRoles] = useState([] as Array<string>);
const [operations, setOperations] = useState([] as Array<Operation>);
const [resourceUpdates, setResourceUpdates] = useState([] as Array<ResourceUpdate>);

const [createFormOpen, setCreateFormOpen] = useState(false);
const [createFormResource, setCreateFormResource] = useState({ resourceType: ResourceType.Workspace } as CreateFormResource);
Expand Down Expand Up @@ -54,32 +52,29 @@ export const App: React.FunctionComponent = () => {
setCreateFormOpen(true);
}
}} >
<NotificationsContext.Provider value={{
<OperationsContext.Provider value={{
operations: operations,
addOperations: (ops: Array<Operation>) => {
let stateOps = [...operations];
ops.forEach((op: Operation) => {
let i = stateOps.findIndex((f: Operation) => f.id === op.id);
if (i > 0) {
if (i !== -1) {
stateOps.splice(i, 1, op);
} else {
stateOps.push(op);
}
});
setOperations(stateOps);
},
resourceUpdates: resourceUpdates,
addResourceUpdate: (r: ResourceUpdate) => {
let updates = [...resourceUpdates];
let i = updates.findIndex((f: ResourceUpdate) => f.resourceId === r.resourceId);
if (i > 0) {
updates.splice(i, 1, r);
} else {
updates.push(r);
}
setResourceUpdates(updates);
},
clearUpdatesForResource: (resourceId: string) => { let updates = [...resourceUpdates].filter((r: ResourceUpdate) => r.resourceId !== resourceId); setResourceUpdates(updates); }
dismissCompleted: () => {
let stateOps = [...operations];
stateOps.forEach((o:Operation) => {
if(completedStates.includes(o.status)) {
o.dismiss = true;
}
})
setOperations(stateOps);
}
}}>
<AppRolesContext.Provider value={{
roles: appRoles,
Expand All @@ -105,9 +100,9 @@ export const App: React.FunctionComponent = () => {
<Route path="/workspaces/:workspaceId//*" element={
<WorkspaceContext.Provider value={{
roles: workspaceRoles,
setRoles: (roles: Array<string>) => { console.warn("Workspace roles", roles); setWorkspaceRoles(roles) },
setRoles: (roles: Array<string>) => { console.info("Workspace roles", roles); setWorkspaceRoles(roles) },
workspace: selectedWorkspace,
setWorkspace: (w: Workspace) => { console.warn("Workspace set", w); setSelectedWorkspace(w) },
setWorkspace: (w: Workspace) => { console.info("Workspace set", w); setSelectedWorkspace(w) },
workspaceApplicationIdURI: selectedWorkspace.properties?.scope_id
}}>
<WorkspaceProvider />
Expand All @@ -121,7 +116,7 @@ export const App: React.FunctionComponent = () => {
</Stack.Item>
</Stack>
</AppRolesContext.Provider>
</NotificationsContext.Provider>
</OperationsContext.Provider>
</CreateUpdateResourceContext.Provider>
</MsalAuthenticationTemplate>
} />
Expand Down
4 changes: 2 additions & 2 deletions ui/app/src/components/shared/ConfirmDeleteResource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useContext, useState } from 'react';
import { Resource } from '../../models/resource';
import { HttpMethod, ResultType, useAuthApiCall } from '../../hooks/useAuthApiCall';
import { WorkspaceContext } from '../../contexts/WorkspaceContext';
import { NotificationsContext } from '../../contexts/NotificationsContext';
import { OperationsContext } from '../../contexts/OperationsContext';
import { ResourceType } from '../../models/resourceType';

interface ConfirmDeleteProps {
Expand All @@ -16,7 +16,7 @@ export const ConfirmDeleteResource: React.FunctionComponent<ConfirmDeleteProps>
const apiCall = useAuthApiCall();
const [isSending, setIsSending] = useState(false);
const workspaceCtx = useContext(WorkspaceContext);
const opsCtx = useContext(NotificationsContext);
const opsCtx = useContext(OperationsContext);

const deleteProps = {
type: DialogType.normal,
Expand Down
4 changes: 2 additions & 2 deletions ui/app/src/components/shared/ConfirmDisableEnableResource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useContext, useState } from 'react';
import { Resource } from '../../models/resource';
import { HttpMethod, ResultType, useAuthApiCall } from '../../hooks/useAuthApiCall';
import { WorkspaceContext } from '../../contexts/WorkspaceContext';
import { NotificationsContext } from '../../contexts/NotificationsContext';
import { OperationsContext } from '../../contexts/OperationsContext';
import { ResourceType } from '../../models/resourceType';

interface ConfirmDisableEnableResourceProps {
Expand All @@ -17,7 +17,7 @@ export const ConfirmDisableEnableResource: React.FunctionComponent<ConfirmDisabl
const apiCall = useAuthApiCall();
const [isSending, setIsSending] = useState(false);
const workspaceCtx = useContext(WorkspaceContext);
const opsCtx = useContext(NotificationsContext);
const opsCtx = useContext(OperationsContext);

const disableProps = {
type: DialogType.normal,
Expand Down
3 changes: 1 addition & 2 deletions ui/app/src/components/shared/ResourceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const ResourceCard: React.FunctionComponent<ResourceCardProps> = (props:
const [loading] = useState(false);
const [showInfo, setShowInfo] = useState(false);
const workspaceCtx = useContext(WorkspaceContext);

const latestUpdate = useComponentManager(
props.resource,
(r: Resource) => { props.onUpdate(r) },
Expand Down Expand Up @@ -127,7 +126,7 @@ export const ResourceCard: React.FunctionComponent<ResourceCardProps> = (props:
}
</Stack.Item>
<Stack.Item style={{ paddingTop: 2, paddingLeft: 10 }}>
<StatusBadge resourceId={props.resource.id} status={latestUpdate.operation ? latestUpdate.operation?.status : props.resource.deploymentStatus} />
<StatusBadge resourceId={props.resource.id} status={latestUpdate.operation?.status ? latestUpdate.operation.status : props.resource.deploymentStatus} />
</Stack.Item>
</Stack>
</Stack.Item>
Expand Down
4 changes: 2 additions & 2 deletions ui/app/src/components/shared/ResourceContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CommandBar, IconButton, IContextualMenuItem, IContextualMenuProps } fro
import { RoleName, WorkspaceRoleName } from '../../models/roleNames';
import { SecuredByRole } from './SecuredByRole';
import { ResourceType } from '../../models/resourceType';
import { NotificationsContext } from '../../contexts/NotificationsContext';
import { OperationsContext } from '../../contexts/OperationsContext';
import { HttpMethod, useAuthApiCall } from '../../hooks/useAuthApiCall';
import { WorkspaceContext } from '../../contexts/WorkspaceContext';
import { ApiEndpoint } from '../../models/apiEndpoints';
Expand All @@ -30,7 +30,7 @@ export const ResourceContextMenu: React.FunctionComponent<ResourceContextMenuPro
const [showDelete, setShowDelete] = useState(false);
const [resourceTemplate, setResourceTemplate] = useState({} as ResourceTemplate);
const createFormCtx = useContext(CreateUpdateResourceContext);
const opsWriteContext = useRef(useContext(NotificationsContext)); // useRef to avoid re-running a hook on context write
const opsWriteContext = useRef(useContext(OperationsContext)); // useRef to avoid re-running a hook on context write
const [parentResource, setParentResource] = useState({} as WorkspaceService | Workspace);

// get the resource template
Expand Down
2 changes: 1 addition & 1 deletion ui/app/src/components/shared/ResourceHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const ResourceHeader: React.FunctionComponent<ResourceHeaderProps> = (pro
{
(props.latestUpdate.operation || props.resource.deploymentStatus) &&
<Stack.Item align="center">
<StatusBadge resourceId={props.resource.id} status={props.latestUpdate.operation ? props.latestUpdate.operation?.status : props.resource.deploymentStatus} />
<StatusBadge resourceId={props.resource.id} status={props.latestUpdate.operation?.status ? props.latestUpdate.operation.status : props.resource.deploymentStatus} />
</Stack.Item>
}
</Stack>
Expand Down
4 changes: 1 addition & 3 deletions ui/app/src/components/shared/ResourcePropertyPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DefaultPalette, getTheme, IStackItemStyles, IStackStyles, Stack } from "@fluentui/react";
import { DefaultPalette, IStackItemStyles, IStackStyles, Stack } from "@fluentui/react";
import moment from "moment";
import React from "react";
import { Resource } from "../../models/resource";
Expand Down Expand Up @@ -94,5 +94,3 @@ export const ResourcePropertyPanel: React.FunctionComponent<ResourcePropertyPane
</> : <></>
);
};

const theme = getTheme();
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Operation } from '../../../models/operation';
import { ResourceType } from '../../../models/resourceType';
import { Workspace } from '../../../models/workspace';
import { WorkspaceService } from '../../../models/workspaceService';
import { NotificationsContext } from '../../../contexts/NotificationsContext';
import { OperationsContext } from '../../../contexts/OperationsContext';
import { ResourceForm } from './ResourceForm';
import { SelectTemplate } from './SelectTemplate';
import { getResourceFromResult, Resource } from '../../../models/resource';
Expand Down Expand Up @@ -41,7 +41,7 @@ export const CreateUpdateResource: React.FunctionComponent<CreateUpdateResourceP
const [page, setPage] = useState('selectTemplate' as keyof PageTitle);
const [selectedTemplate, setTemplate] = useState(props.updateResource?.templateName || '');
const [deployOperation, setDeployOperation] = useState({} as Operation);
const opsContext = useContext(NotificationsContext);
const opsContext = useContext(OperationsContext);
const navigate = useNavigate();
const apiCall = useAuthApiCall();

Expand Down
Loading