Skip to content

Commit

Permalink
Airlock UI: add title to airlock request (#2731)
Browse files Browse the repository at this point in the history
* added title textbox

* modified airlock request models to add title

* added requestTitle to UI

* modified airlock_request api model

* additions for airlock import title task

Co-authored-by: James Griffin <jjgriff93@users.noreply.github.com>

* added request title to db

* added request title to sample airlock req

* Added title field to table

* Fix warnings

* Amended tests

* Update version & changelog

Co-authored-by: Hizni Salih <hizni.salih@gmail.com>
Co-authored-by: James Griffin <jjgriff93@users.noreply.github.com>
Co-authored-by: Hizni Salih <4255268+hizni@users.noreply.github.com>
Co-authored-by: jjgriff93 <jamesgr@microsoft.com>
  • Loading branch information
5 people authored Oct 13, 2022
1 parent 680d35a commit 36acf13
Show file tree
Hide file tree
Showing 14 changed files with 142 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

FEATURES:
* Added filtering and sorting to Airlock UI ([#2511](https://github.com/microsoft/AzureTRE/issues/2511))
* Added title field to Airlock requests ([#2731](https://github.com/microsoft/AzureTRE/pull/2731))

ENHANCEMENTS:

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.52"
__version__ = "0.4.53"
1 change: 1 addition & 0 deletions api_app/db/repositories/airlock_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def create_airlock_request_item(self, airlock_request_input: AirlockRequestInCre
airlock_request = AirlockRequest(
id=full_airlock_request_id,
workspaceId=workspace_id,
requestTitle=airlock_request_input.requestTitle,
businessJustification=airlock_request_input.businessJustification,
requestType=airlock_request_input.requestType,
creationTime=datetime.utcnow().timestamp(),
Expand Down
1 change: 1 addition & 0 deletions api_app/models/domain/airlock_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class AirlockRequest(AzureTREModel):
workspaceId: str = Field("", title="Workspace ID", description="Service target Workspace id")
requestType: AirlockRequestType = Field("", title="Airlock request type")
files: List[AirlockFile] = Field([], title="Files of the request")
requestTitle: str = Field("Airlock Request", title="Brief title for the request")
businessJustification: str = Field("Business Justifications", title="Explanation that will be provided to the request reviewer")
status = AirlockRequestStatus.Draft
creationTime: float = Field(None, title="Creation time of the request")
Expand Down
3 changes: 3 additions & 0 deletions api_app/models/schemas/airlock_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def get_sample_airlock_request(workspace_id: str, airlock_request_id: str) -> di
"status": "draft",
"requestType": "import",
"files": [],
"requestTitle": "a request title",
"businessJustification": "some business justification",
"creationTime": datetime.utcnow().timestamp(),
"reviews": [
Expand Down Expand Up @@ -72,13 +73,15 @@ class Config:

class AirlockRequestInCreate(BaseModel):
requestType: AirlockRequestType = Field("", title="Airlock request type", description="Specifies if this is an import or an export request")
requestTitle: str = Field("Airlock Request", title="Brief title for the request")
businessJustification: str = Field("Business Justifications", title="Explanation that will be provided to the request reviewer")
properties: dict = Field({}, title="Airlock request parameters", description="Values for the parameters required by the Airlock request specification")

class Config:
schema_extra = {
"example": {
"requestType": "import",
"requestTitle": "a request title",
"businessJustification": "some business justification"
}
}
Expand Down
2 changes: 2 additions & 0 deletions api_app/tests_ma/test_api/test_routes/test_airlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def sample_airlock_request_object(status=AirlockRequestStatus.Draft, airlock_req
airlock_request = AirlockRequest(
id=airlock_request_id,
workspaceId=workspace_id,
requestTitle="test title",
businessJustification="test business justification",
requestType="import",
status=status
Expand All @@ -45,6 +46,7 @@ def sample_airlock_request_object_with_review(status=AirlockRequestStatus.Draft,
airlock_request = AirlockRequest(
id=airlock_request_id,
workspaceId=workspace_id,
requestTitle="test title",
businessJustification="test business justification",
requestType="import",
status=status,
Expand Down
1 change: 1 addition & 0 deletions ui/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"@azure/msal-browser": "^2.24.0",
"@azure/msal-react": "^1.4.0",
"@fluentui/react": "^8.68.2",
"@fluentui/react-file-type-icons": "^8.7.9",
"@reduxjs/toolkit": "^1.8.6",
"@rjsf/core": "^4.2.0",
"@rjsf/fluent-ui": "^4.2.0",
Expand Down
17 changes: 11 additions & 6 deletions ui/app/src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ ul.tre-notifications-steps-list li {
}
}

.tre-table {
.ms-Persona-primaryText {
font-size: 12px;
color: rgb(108, 108, 108);
}

.ms-DetailsRow-cell {
align-self: baseline;
}
}

.tre-hide-chevron i[data-icon-name=ChevronDown] {
display: none;
}
Expand Down Expand Up @@ -158,12 +169,6 @@ ul.tre-notifications-steps-list li {
background-color: #fff;
}

.tre-table-rows-align-centre {
.ms-DetailsRow-cell {
align-self: baseline;
}
}

.ms-Pivot {
margin-bottom: 10px;
}
Expand Down
4 changes: 4 additions & 0 deletions ui/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CreateUpdateResource } from './components/shared/create-update-resource
import { CreateUpdateResourceContext } from './contexts/CreateUpdateResourceContext';
import { CreateFormResource, ResourceType } from './models/resourceType';
import { Footer } from './components/shared/Footer';
import { initializeFileTypeIcons } from '@fluentui/react-file-type-icons';

export const App: React.FunctionComponent = () => {
const [appRoles, setAppRoles] = useState([] as Array<string>);
Expand All @@ -38,6 +39,9 @@ export const App: React.FunctionComponent = () => {
setAppRolesOnLoad();
}, [apiCall]);

// initiliase filetype icons
useEffect(() => initializeFileTypeIcons(), []);

return (
<>
<Routes>
Expand Down
31 changes: 24 additions & 7 deletions ui/app/src/components/shared/airlock/Airlock.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { ColumnActionsMode, CommandBar, CommandBarButton, ContextualMenu, DirectionalHint, getTheme, IColumn, ICommandBarItemProps, IContextualMenuItem, IContextualMenuProps, Label, Persona, PersonaSize, SelectionMode, ShimmeredDetailsList, Stack } from '@fluentui/react';
import { ColumnActionsMode, CommandBar, CommandBarButton, ContextualMenu, DirectionalHint, getTheme, IColumn, ICommandBarItemProps, Icon, IContextualMenuItem, IContextualMenuProps, Persona, PersonaSize, SelectionMode, ShimmeredDetailsList, Stack } from '@fluentui/react';
import { HttpMethod, useAuthApiCall } from '../../../hooks/useAuthApiCall';
import { ApiEndpoint } from '../../../models/apiEndpoints';
import { WorkspaceContext } from '../../../contexts/WorkspaceContext';
Expand All @@ -13,6 +13,7 @@ import { ExceptionLayout } from '../ExceptionLayout';
import { AirlockNewRequest } from './AirlockNewRequest';
import { WorkspaceRoleName } from '../../../models/roleNames';
import { useAccount, useMsal } from '@azure/msal-react';
import { getFileTypeIconProps } from '@fluentui/react-file-type-icons';

export const Airlock: React.FunctionComponent = () => {
const [airlockRequests, setAirlockRequests] = useState([] as AirlockRequest[]);
Expand All @@ -27,7 +28,7 @@ export const Airlock: React.FunctionComponent = () => {
const apiCall = useAuthApiCall();
const theme = getTheme();
const navigate = useNavigate();
const { instance, accounts } = useMsal();
const { accounts } = useMsal();
const account = useAccount(accounts[0] || {});

// Get the airlock request data from API
Expand Down Expand Up @@ -151,15 +152,31 @@ export const Airlock: React.FunctionComponent = () => {

const columns: IColumn[] = [
{
key: 'avatar',
name: '',
key: 'fileIcon',
name: 'fileIcon',
minWidth: 16,
maxWidth: 16,
isIconOnly: true,
onRender: (request: AirlockRequest) => {
return <Persona size={ PersonaSize.size24 } text={ request.user?.name } />
if (request.status === AirlockRequestStatus.Draft) {
return <Icon iconName="FolderOpen" style={{verticalAlign:'bottom', fontSize: 14}} />
} else if (request.files?.length > 0 && request.files[0].name) {
const fileType = request.files[0].name.split('.').pop();
return <Icon {...getFileTypeIconProps({ extension: fileType })} style={{verticalAlign:'bottom'}} />
} else {
return <Icon iconName="Page" style={{verticalAlign:'bottom', fontSize: 14}} />
}
}
},
{
key: 'title',
name: 'Title',
ariaLabel: 'Title of the airlock request',
minWidth: 150,
maxWidth: 300,
isResizable: true,
fieldName: 'requestTitle'
},
{
key: 'creator_user_id',
name: 'Initiator',
Expand All @@ -168,7 +185,7 @@ export const Airlock: React.FunctionComponent = () => {
maxWidth: 200,
isResizable: true,
fieldName: 'initiator',
onRender: (request: AirlockRequest) => request.user?.name,
onRender: (request: AirlockRequest) => <Persona size={ PersonaSize.size24 } text={ request.user?.name } />,
isFiltered: filters.has('creator_user_id')
},
{
Expand Down Expand Up @@ -310,7 +327,7 @@ export const Airlock: React.FunctionComponent = () => {
selectionMode={SelectionMode.none}
getKey={(item) => item?.id}
onItemInvoked={(item) => navigate(item.id)}
className="tre-table-rows-align-centre"
className="tre-table"
enableShimmer={loadingState === LoadingState.Loading}
/>
{
Expand Down
25 changes: 24 additions & 1 deletion ui/app/src/components/shared/airlock/AirlockNewRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ export const AirlockNewRequest: React.FunctionComponent<AirlockNewRequestProps>
const workspaceCtx = useContext(WorkspaceContext);
const apiCall = useAuthApiCall();

const onChangeRequestTitle = useCallback(
(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
setNewRequest(request => {
return {
...request,
requestTitle: newValue || ''
}
});
},
[setNewRequest]
);

const onChangeBusinessJustification = useCallback(
(event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
setNewRequest(request => {
Expand All @@ -36,7 +48,10 @@ export const AirlockNewRequest: React.FunctionComponent<AirlockNewRequestProps>
);

useEffect(
() => setRequestValid(newRequest.businessJustification?.length > 0),
() => setRequestValid(
newRequest.requestTitle?.length > 0 &&
newRequest.businessJustification?.length > 0
),
[newRequest, setRequestValid]
);

Expand Down Expand Up @@ -118,6 +133,14 @@ export const AirlockNewRequest: React.FunctionComponent<AirlockNewRequestProps>
} else {
title = `New airlock ${newRequest.requestType} request`;
currentStep = <Stack style={{marginTop: '40px'}} tokens={stackTokens}>
<TextField
label="Title"
placeholder="Enter a request title."
value={newRequest.requestTitle}
onChange={onChangeRequestTitle}
rows={1}
required
/>
<TextField
label="Business Justification"
placeholder="Enter a justification for your request."
Expand Down
4 changes: 2 additions & 2 deletions ui/app/src/components/shared/airlock/AirlockViewRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const AirlockViewRequest: React.FunctionComponent<AirlockViewRequestProps
} else {
setRequest(req);
}
}, [apiCall, requestId, props.requests]);
}, [apiCall, requestId, props.requests, workspaceCtx.workspace.id, workspaceCtx.workspaceApplicationIdURI]);

const generateFilesLink = useCallback(async () => {
// Retrieve a link to view/edit the airlock files
Expand Down Expand Up @@ -177,7 +177,7 @@ export const AirlockViewRequest: React.FunctionComponent<AirlockViewRequestProps
return (
<>
<Panel
headerText="View Airlock Request"
headerText={request && request.requestTitle ? request.requestTitle : "View airlock request"}
isOpen={true}
isLightDismiss={true}
onDismiss={dismissPanel}
Expand Down
4 changes: 3 additions & 1 deletion ui/app/src/models/airlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { User } from "./user";
export interface AirlockRequest extends Resource {
workspaceId: string;
requestType: AirlockRequestType;
files: Array<string>;
files: Array<{name: string, size: number}>;
requestTitle: string;
businessJustification: string;
statusMessage: null | string;
status: AirlockRequestStatus;
Expand Down Expand Up @@ -34,6 +35,7 @@ export enum AirlockRequestStatus {

export interface NewAirlockRequest {
requestType: AirlockRequestType;
requestTitle: string;
businessJustification: string;
}

Expand Down
64 changes: 64 additions & 0 deletions ui/app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1223,6 +1223,14 @@
"@fluentui/set-version" "^8.2.1"
tslib "^2.1.0"

"@fluentui/dom-utilities@^2.2.2":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@fluentui/dom-utilities/-/dom-utilities-2.2.2.tgz#9fffd59a1ceab7121bbb4355744d94cae5452780"
integrity sha512-puklLc6Jvg279OGagqkSfuHML6ckBhw3gJakdvIZHKeJiduh+34U4Finl3K24yBSXzG2WsN+LwLTd1Vcociy+g==
dependencies:
"@fluentui/set-version" "^8.2.2"
tslib "^2.1.0"

"@fluentui/font-icons-mdl2@^8.4.1":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.4.1.tgz#d40bf884752979643d4936c0bb55a90a243c37e5"
Expand Down Expand Up @@ -1259,6 +1267,23 @@
"@fluentui/set-version" "^8.2.1"
tslib "^2.1.0"

"@fluentui/merge-styles@^8.5.3":
version "8.5.3"
resolved "https://registry.yarnpkg.com/@fluentui/merge-styles/-/merge-styles-8.5.3.tgz#e9d9d08a4825e117913c417d99233736f570736a"
integrity sha512-bHWftN3zTp1bbBfmAEH8YK9UURWj2mffw7b7VaW2Og1qxwv3GMSza1cyv/d3EVqpMJ8AVwFv3mbi9p1ieMN9mw==
dependencies:
"@fluentui/set-version" "^8.2.2"
tslib "^2.1.0"

"@fluentui/react-file-type-icons@^8.7.9":
version "8.7.9"
resolved "https://registry.yarnpkg.com/@fluentui/react-file-type-icons/-/react-file-type-icons-8.7.9.tgz#9992af91395474d25804b86cb6e44e6b1f3faf41"
integrity sha512-EOZnXBkkS6BJmrChCy1cVT1hkvi7JuesxbqLCrEsyxEk4jn9aha9XkAR2JV44h+bB7p2iFJUBzfZaWK95d0xJA==
dependencies:
"@fluentui/set-version" "^8.2.2"
"@fluentui/style-utilities" "^8.8.0"
tslib "^2.1.0"

"@fluentui/react-focus@^8.7.0":
version "8.7.0"
resolved "https://registry.yarnpkg.com/@fluentui/react-focus/-/react-focus-8.7.0.tgz#5319aff831fa40149e674d7e7a434b3d5d26f434"
Expand Down Expand Up @@ -1323,6 +1348,13 @@
dependencies:
tslib "^2.1.0"

"@fluentui/set-version@^8.2.2":
version "8.2.2"
resolved "https://registry.yarnpkg.com/@fluentui/set-version/-/set-version-8.2.2.tgz#37cffcda607cb2604a86b72e98aee94c2fb809d2"
integrity sha512-Vg20KZ0ufgWjxx6GFbqC5wiVxXZDUWgNT0r0By/Eyj4bUSb1jG6lmf5z1oY1dUX0YS6Cp5e6GnvbNdXg5E7orA==
dependencies:
tslib "^2.1.0"

"@fluentui/style-utilities@^8.7.0":
version "8.7.0"
resolved "https://registry.yarnpkg.com/@fluentui/style-utilities/-/style-utilities-8.7.0.tgz#bc5a0ff17ae0d4dadc9c5b4b7f58c2abdf799667"
Expand All @@ -1335,6 +1367,28 @@
"@microsoft/load-themed-styles" "^1.10.26"
tslib "^2.1.0"

"@fluentui/style-utilities@^8.8.0":
version "8.8.0"
resolved "https://registry.yarnpkg.com/@fluentui/style-utilities/-/style-utilities-8.8.0.tgz#7430d34774a87eb776a819dc36d0627e0957a795"
integrity sha512-wqntrpzOGvBNojAlnXVQB98hYQkS0g5ZckF/JxkNDWYRUcemu9bUTgBOg1hdiV9DM8nxyg34LE794oMxRIuLHA==
dependencies:
"@fluentui/merge-styles" "^8.5.3"
"@fluentui/set-version" "^8.2.2"
"@fluentui/theme" "^2.6.16"
"@fluentui/utilities" "^8.13.1"
"@microsoft/load-themed-styles" "^1.10.26"
tslib "^2.1.0"

"@fluentui/theme@^2.6.16":
version "2.6.16"
resolved "https://registry.yarnpkg.com/@fluentui/theme/-/theme-2.6.16.tgz#a29c58bf16f765ba1b97a497e572fd1fdc469c44"
integrity sha512-Ml2oMVvoOxRYD9HPjEkGCWvnQnzDyrufa5k8bPYN8xjJbbEGtDjjswcfrSVfHx1fCR1CFgybHR8jj3pvXRTXUQ==
dependencies:
"@fluentui/merge-styles" "^8.5.3"
"@fluentui/set-version" "^8.2.2"
"@fluentui/utilities" "^8.13.1"
tslib "^2.1.0"

"@fluentui/theme@^2.6.6":
version "2.6.6"
resolved "https://registry.yarnpkg.com/@fluentui/theme/-/theme-2.6.6.tgz#282b6f1d4f564c43fc526f30aa1ae26b26b62f3f"
Expand All @@ -1345,6 +1399,16 @@
"@fluentui/utilities" "^8.8.3"
tslib "^2.1.0"

"@fluentui/utilities@^8.13.1":
version "8.13.1"
resolved "https://registry.yarnpkg.com/@fluentui/utilities/-/utilities-8.13.1.tgz#028fd2e93f68b5f386039970dce0ae642eb7e9da"
integrity sha512-BpLa0lSYnZ3YoTGB6T/pQ0vUVq0PEr6gF+daptyeiLUkEXVoy3HYgX6ZanA62wJ89ycIwI8A+1aUEbmtDMupYg==
dependencies:
"@fluentui/dom-utilities" "^2.2.2"
"@fluentui/merge-styles" "^8.5.3"
"@fluentui/set-version" "^8.2.2"
tslib "^2.1.0"

"@fluentui/utilities@^8.8.3":
version "8.8.3"
resolved "https://registry.yarnpkg.com/@fluentui/utilities/-/utilities-8.8.3.tgz#d41fd5f8ed96baa2b3d70853a0bf1ab6bfa1936d"
Expand Down

0 comments on commit 36acf13

Please sign in to comment.