Skip to content

Commit

Permalink
Merge 988f39f into 3f2b7df
Browse files Browse the repository at this point in the history
  • Loading branch information
nbarlowATI committed Apr 25, 2022
2 parents 3f2b7df + 988f39f commit 64faae0
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 47 deletions.
22 changes: 22 additions & 0 deletions eap_backend/eap_api/migrations/0015_auto_20220421_1553.py
@@ -0,0 +1,22 @@
# Generated by Django 3.2.8 on 2022-04-21 15:53

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("eap_api", "0014_propertyclaim_claim_type"),
]

operations = [
migrations.RemoveField(
model_name="assurancecase",
name="editable",
),
migrations.AddField(
model_name="assurancecase",
name="lock_uuid",
field=models.CharField(blank=True, default=None, max_length=50, null=True),
),
]
2 changes: 1 addition & 1 deletion eap_backend/eap_api/models.py
Expand Up @@ -39,7 +39,7 @@ class AssuranceCase(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=1000)
created_date = models.DateTimeField(auto_now_add=True)
editable = models.BooleanField(default=True)
lock_uuid = models.CharField(max_length=50, default=None, null=True, blank=True)
shape = None

def __str__(self):
Expand Down
2 changes: 1 addition & 1 deletion eap_backend/eap_api/serializers.py
Expand Up @@ -22,7 +22,7 @@ class Meta:
"name",
"description",
"created_date",
"editable",
"lock_uuid",
"goals",
)

Expand Down
2 changes: 1 addition & 1 deletion eap_backend/eap_api/views.py
Expand Up @@ -28,7 +28,7 @@
"serializer": AssuranceCaseSerializer,
"model": AssuranceCase,
"children": ["goals"],
"fields": ("name", "description", "editable"),
"fields": ("name", "description", "lock_uuid"),
},
"goal": {
"serializer": TopLevelNormativeGoalSerializer,
Expand Down
2 changes: 1 addition & 1 deletion eap_backend/tests/test_views.py
Expand Up @@ -54,7 +54,7 @@ def test_case_list_view_post(self):
post_data = {
"name": "newCASE",
"description": "new description",
"editable": True,
"lock_uuid": None,
}
response_post = self.client.post(
reverse("case_list"),
Expand Down
7 changes: 7 additions & 0 deletions frontend/Dockerfile
@@ -0,0 +1,7 @@
FROM node:16.14.2-slim
COPY . /frontend
WORKDIR /frontend
RUN npm install
RUN npm run build
RUN npm install serve -g
ENTRYPOINT ["serve","-s","build"]
1 change: 1 addition & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Expand Up @@ -24,6 +24,7 @@
"react-zoom-pan-pinch": "^2.1.3",
"regenerator-runtime": "^0.13.9",
"styled-components": "^5.3.3",
"uuid": "^8.3.2",
"web-vitals": "^1.1.2"
},
"jest": {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/caseTemplates/empty.json
@@ -1,6 +1,6 @@
{
"name": "Empty",
"description": "N/A",
"editable": true,
"lock_uuid": null,
"goals": []
}
2 changes: 1 addition & 1 deletion frontend/src/caseTemplates/minimal.json
@@ -1,7 +1,7 @@
{
"name": "Minimal",
"description": "N/A",
"editable": true,
"lock_uuid": null,
"goals": [
{
"name": "Goal",
Expand Down
172 changes: 137 additions & 35 deletions frontend/src/components/CaseContainer.js
Expand Up @@ -4,6 +4,7 @@ import { useNavigate, useParams } from "react-router-dom";
import { Grid, Box, DropButton, Layer, Button, Text } from "grommet";
import { FormClose, ZoomIn, ZoomOut } from "grommet-icons";
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
import { v4 as uuidv4 } from "uuid";

import MermaidChart from "./Mermaid";
import EditableText from "./EditableText.js";
Expand Down Expand Up @@ -53,6 +54,21 @@ class CaseContainer extends Component {
}
};

submitCaseChange(field, value) {
// Send to the backend a PUT request, changing the `field` of the current case to be
// `value`.
const id = this.state.assurance_case.id;
const backendURL = `${configData.BASE_URL}/cases/${id}/`;
const changeObj = {};
changeObj[field] = value;
const requestOptions = {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(changeObj),
};
return fetch(backendURL, requestOptions);
}

deleteCurrentCase() {
const id = this.state.assurance_case.id;
const backendURL = `${configData.BASE_URL}/cases/${id}/`;
Expand Down Expand Up @@ -99,11 +115,33 @@ class CaseContainer extends Component {
this.setState({ id: id });
this.fetchData(id);
this.timer = setInterval(() => this.fetchData(id), 5000);
if (!window.sessionStorage.getItem("session_id")) {
let uuid = uuidv4();
window.sessionStorage.setItem("session_id", uuid);
}
this.setState({ session_id: window.sessionStorage.session_id });
// Activate the event listener to see when the browser/tab closes
this.setupBeforeUnloadListener();
}

componentWillUnmount() {
cleanup() {
clearInterval(this.timer);
this.timer = null;
if (this.state.assurance_case.lock_uuid == this.state.session_id) {
this.submitCaseChange("lock_uuid", null);
}
}

// Setup the `beforeunload` event listener to detect browser/tab closing
setupBeforeUnloadListener = () => {
window.addEventListener("beforeunload", (ev) => {
ev.preventDefault();
return this.cleanup();
});
};

componentWillUnmount() {
this.cleanup();
}

componentDidUpdate(prevProps) {
Expand All @@ -117,11 +155,10 @@ class CaseContainer extends Component {
updateView() {
// render() will be called again anytime setState is called, which
// is done both by hideEditLayer() and hideCreateLayer()

this.hideViewLayer();
this.hideEditLayer();
this.hideCreateLayer();
this.fetchData(this.state.id);
return this.fetchData(this.state.id);
}

showViewLayer(e) {
Expand Down Expand Up @@ -216,6 +253,7 @@ class CaseContainer extends Component {
id={this.state.itemId}
editItemLayer={this.showEditLayer.bind(this)}
updateView={this.updateView.bind(this)}
editMode={this.inEditMode()}
/>
</Box>
</Box>
Expand Down Expand Up @@ -260,21 +298,6 @@ class CaseContainer extends Component {
);
}

submitCaseChange(field, value) {
// Send to the backend a PUT request, changing the `field` of the current case to be
// `value`.
const id = this.state.assurance_case.id;
const backendURL = `${configData.BASE_URL}/cases/${id}/`;
const changeObj = {};
changeObj[field] = value;
const requestOptions = {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(changeObj),
};
fetch(backendURL, requestOptions);
}

createLayer() {
return (
<Box>
Expand Down Expand Up @@ -347,6 +370,79 @@ class CaseContainer extends Component {
);
}

enableEditing() {
if (!this.state.assurance_case.lock_uuid) {
this.submitCaseChange("lock_uuid", this.state.session_id).then(
(response) => {
this.updateView();
}
);
} else if (this.state.assurance_case.lock_uuid !== this.state.session_id) {
// override!
if (
window.confirm(
"Are you sure? You might be overwriting someone's work..."
)
) {
this.submitCaseChange("lock_uuid", this.state.session_id).then(
(response) => {
this.updateView();
}
);
}
}
}

disableEditing() {
if (this.state.assurance_case.lock_uuid) {
this.submitCaseChange("lock_uuid", null).then((response) => {
this.updateView();
});
}
}

inEditMode() {
return this.state.assurance_case.lock_uuid === this.state.session_id;
}

getEditableControls() {
if (this.inEditMode()) {
return (
<Button
label="Disable edit mode"
secondary
onClick={this.disableEditing.bind(this)}
/>
);
} else if (!this.state.assurance_case.lock_uuid) {
return (
<Button
label="Enable edit mode"
secondary
onClick={this.enableEditing.bind(this)}
/>
);
} else {
return (
<span>
<p>
<Text color="#ff0000">
Someone else is currently editing this case.
</Text>
</p>
<p>
<Button
label="Override - enable edit mode"
color="#ff0000"
secondary
onClick={this.enableEditing.bind(this)}
/>
</p>
</span>
);
}
}

render() {
// don't try to render the chart until we're sure we have the full JSON from the DB
if (this.state.loading) {
Expand Down Expand Up @@ -408,6 +504,7 @@ class CaseContainer extends Component {
this.submitCaseChange("description", value)
}
/>
{this.getEditableControls()}
</Box>

<Box
Expand All @@ -420,28 +517,33 @@ class CaseContainer extends Component {
bottom: "none",
}}
>
<Button
label="Delete Case"
secondary
onClick={this.showConfirmDeleteLayer.bind(this)}
/>
{this.inEditMode() && (
<Button
label="Delete Case"
secondary
onClick={this.showConfirmDeleteLayer.bind(this)}
/>
)}

<Button
label="Export"
secondary
onClick={this.exportCurrentCase.bind(this)}
/>
<DropButton
label="Add Goal"
dropAlign={{ top: "bottom", right: "right" }}
dropContent={
<ItemCreator
type="TopLevelNormativeGoal"
parentId={this.state.id}
parentType="AssuranceCase"
updateView={this.updateView.bind(this)}
/>
}
/>
{this.inEditMode() && (
<DropButton
label="Add Goal"
dropAlign={{ top: "bottom", right: "right" }}
dropContent={
<ItemCreator
type="TopLevelNormativeGoal"
parentId={this.state.id}
parentType="AssuranceCase"
updateView={this.updateView.bind(this)}
/>
}
/>
)}
</Box>

<Box
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/components/ItemViewer.js
Expand Up @@ -59,12 +59,14 @@ function ItemViewer(props) {
<p>{items.URL}</p>
</Box>
)}
<Box>
<Button
onClick={(e) => props.editItemLayer(props.type, props.id, e)}
label="Edit"
/>
</Box>
{props.editMode && (
<Box>
<Button
onClick={(e) => props.editItemLayer(props.type, props.id, e)}
label="Edit"
/>
</Box>
)}
</Box>
);
}
Expand Down

0 comments on commit 64faae0

Please sign in to comment.