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

View revision details #1327

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
12 changes: 12 additions & 0 deletions backend/api/Controllers/RevisionsController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using api.Authorization;
using api.Dtos;
using api.Models;
using api.Services;

using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -44,4 +45,15 @@ public async Task<ProjectWithAssetsDto> CreateProject([FromRoute] Guid projectId
{
return await _revisionService.CreateRevision(projectId, createRevisionDto);
}

[HttpPut("{revisionId}")]
[RequiresApplicationRoles(
ApplicationRole.Admin,
ApplicationRole.User
)]
[ActionType(ActionType.Edit)]
public async Task<ProjectDto> UpdateRevision([FromRoute] Guid projectId, [FromRoute] Guid revisionId, [FromBody] UpdateRevisionDto updateRevisionDto)
{
return await _revisionService.UpdateRevision(projectId, revisionId, updateRevisionDto);
}
}
10 changes: 10 additions & 0 deletions backend/api/Dtos/Project/UpdateRevisionDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

using api.Models;

namespace api.Dtos;

public class UpdateRevisionDto
{
public string Name { get; set; } = null!;

}
2 changes: 2 additions & 0 deletions backend/api/Services/Entities/IRevisionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ public interface IRevisionService
{
Task<ProjectWithAssetsDto> GetRevision(Guid projectId);
Task<ProjectWithAssetsDto> CreateRevision(Guid projectId, CreateRevisionDto createRevisionDto);
Task<ProjectDto> UpdateRevision(Guid projectId, Guid revisionId, UpdateRevisionDto updateRevisionDto);

}
27 changes: 26 additions & 1 deletion backend/api/Services/Entities/RevisionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class RevisionService : IRevisionService
private readonly DcdDbContext _context;
private readonly IMapper _mapper;
private readonly IProjectRepository _projectRepository;
private readonly IMapperService _mapperService;


public RevisionService(
Expand All @@ -34,7 +35,8 @@ public RevisionService(
IProjectAccessService projectAccessService,
IProjectService projectService,
IMapper mapper,
IProjectRepository projectRepository
IProjectRepository projectRepository,
IMapperService mapperService
)
{
_context = context;
Expand All @@ -44,6 +46,7 @@ IProjectRepository projectRepository
_projectService = projectService;
_mapper = mapper;
_projectRepository = projectRepository;
_mapperService = mapperService;
}

// TODO: Rewrite when CaseWithAssetsDto is no longer needed
Expand Down Expand Up @@ -249,6 +252,28 @@ private async Task<Project> UpdateProjectWithRevisionChanges(Guid projectId, Cre
return existingProject;
}

public async Task<ProjectDto> UpdateRevision(Guid projectId, Guid revisionId, UpdateRevisionDto updateRevisionDto)
{
var project = await _projectRepository.GetProjectWithCases(projectId)
?? throw new NotFoundInDBException($"Project with id {projectId} not found.");

var revision = project.Revisions?.FirstOrDefault(r => r.Id == revisionId)
?? throw new NotFoundInDBException($"Revision with id {revisionId} not found.");

revision.Name = updateRevisionDto.Name;

_context.Projects.Update(project);
await _context.SaveChangesAsync();

Activity.Current?.AddBaggage(nameof(project), JsonConvert.SerializeObject(project, Formatting.None,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
}));
var projectDto = _mapperService.MapToDto<Project, ProjectDto>(project, projectId);
return projectDto;
}

private void SetProjectAndRelatedEntitiesToEmptyGuids(Project project, Guid originalProjectId, CreateRevisionDto createRevisionDto)
{
project.Id = Guid.Empty;
Expand Down
171 changes: 153 additions & 18 deletions frontend/src/Components/Controls/RevisionDetailsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,180 @@
import React from "react"
import React, {
ChangeEventHandler, useEffect, useRef, useState,

Check failure on line 2 in frontend/src/Components/Controls/RevisionDetailsModal.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

frontend/src/Components/Controls/RevisionDetailsModal.tsx#L2

'useRef' is defined but never used.
} from "react"
import {
Typography, Icon, Button,
Divider,

Check failure on line 6 in frontend/src/Components/Controls/RevisionDetailsModal.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

frontend/src/Components/Controls/RevisionDetailsModal.tsx#L6

'Divider' is defined but never used.
InputWrapper,
TextField,
Chip,
} from "@equinor/eds-core-react"
import { exit_to_app } from "@equinor/eds-icons"
import { checkbox_outline, exit_to_app, info_circle } from "@equinor/eds-icons"

Check failure on line 11 in frontend/src/Components/Controls/RevisionDetailsModal.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

frontend/src/Components/Controls/RevisionDetailsModal.tsx#L11

'exit_to_app' is defined but never used.
import { useQuery, useQueryClient } from "@tanstack/react-query"

Check failure on line 12 in frontend/src/Components/Controls/RevisionDetailsModal.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

frontend/src/Components/Controls/RevisionDetailsModal.tsx#L12

'useQueryClient' is defined but never used.
import { useModuleCurrentContext } from "@equinor/fusion-framework-react-module-context"
import styled from "styled-components"
import { useNavigate, useParams } from "react-router-dom"

Check failure on line 15 in frontend/src/Components/Controls/RevisionDetailsModal.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

frontend/src/Components/Controls/RevisionDetailsModal.tsx#L15

'useNavigate' is defined but never used.
import DialogContent from "@mui/material/DialogContent"
import { Grid } from "@mui/material"
import DialogActions from "@mui/material/DialogActions"
import Modal from "../Modal/Modal"

type CaseDropMenuProps = {
import { formatFullDate } from "@/Utils/common"
import { projectQueryFn, revisionQueryFn } from "@/Services/QueryFunctions"
import { exitRevisionView, updateRevisionName } from "@/Utils/RevisionUtils"

Check failure on line 23 in frontend/src/Components/Controls/RevisionDetailsModal.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

frontend/src/Components/Controls/RevisionDetailsModal.tsx#L23

'exitRevisionView' is defined but never used.
import { useProjectContext } from "@/Context/ProjectContext"
import useEditProject from "@/Hooks/useEditProject"
import { GetProjectService } from "@/Services/ProjectService"

Check failure on line 26 in frontend/src/Components/Controls/RevisionDetailsModal.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

frontend/src/Components/Controls/RevisionDetailsModal.tsx#L26

'GetProjectService' is defined but never used.
import { PROJECT_CLASSIFICATION, INTERNAL_PROJECT_PHASE } from "@/Utils/constants"

type RevisionDetailsModalProps = {
isMenuOpen: boolean;
setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean>>;
};

const CaseDropMenu: React.FC<CaseDropMenuProps> = ({ isMenuOpen, setIsMenuOpen }) => {
const exitRevisionView = () => {
console.log("Exiting revision view")
}
const Wrapper = styled.div`
flex-direction: row;
display: flex;
margin-bottom: 10px;
`

const InfoIcon = styled(Icon)`
margin-right: 5px;
margin-top: -2px;
`

const ColumnWrapper = styled.div`
display: flex;
flex-direction: column;
margin-bottom: 15px;
gap: 8px;
`

const RevisionDetailsModal: React.FC<RevisionDetailsModalProps> = ({
isMenuOpen,
setIsMenuOpen,
}) => {
const { currentContext } = useModuleCurrentContext()
const externalId = currentContext?.externalId
const { projectId } = useProjectContext()
const [isNameChanged, setIsNameChanged] = useState(false)
const { addProjectEdit } = useEditProject()
const [revisionName, setRevisionName] = useState<string>("")

const { revisionId } = useParams()

const { data: projectApiData } = useQuery({
queryKey: ["projectApiData", externalId],
queryFn: () => projectQueryFn(externalId),
enabled: !!externalId,
})

const { data: revisionApiData } = useQuery({
queryKey: ["revisionApiData", revisionId],
queryFn: () => revisionQueryFn(projectId, revisionId),
enabled: !!revisionId,
})

useEffect(() => {
setRevisionName(revisionApiData?.name || "")
}, [revisionApiData])

const closeMenu = () => {
setIsMenuOpen(false)
}

const handleRevisionNameChange = async () => {
if (revisionApiData && projectId && revisionId) {
const updatedRevision = await updateRevisionName(projectId, revisionId, revisionName)
if (updatedRevision) {
addProjectEdit(updatedRevision.id, updatedRevision)
closeMenu()
}
}
}

const handleNameInputChange: ChangeEventHandler<HTMLInputElement> = async (e) => {
setRevisionName(e.currentTarget.value)
setIsNameChanged(true)
}

const newClassification = PROJECT_CLASSIFICATION[revisionApiData?.classification as keyof typeof PROJECT_CLASSIFICATION]?.label
const newInternalProjectPhase = revisionApiData?.internalProjectPhase as keyof typeof INTERNAL_PROJECT_PHASE

if (!projectApiData && !revisionApiData) { return null }

return (
<Modal
title="APx Rev 1. Revision details"
title={`${revisionName} revision details`}
size="sm"
isOpen={isMenuOpen}
content={(
<Typography variant="body_short">
Revisions are copies of a project at a given point in time. Revisions are locked for editing.
</Typography>
<DialogContent>
<Wrapper>
<InfoIcon data={info_circle} size={18} />
<Typography group="ui" variant="chart">
Revisions are copies of a project at a given point in time.
Revisions are locked for editing.
</Typography>
</Wrapper>
<Grid item xs={12} md={8}>
<ColumnWrapper>
<InputWrapper labelProps={{ label: "Revision name" }}>
<TextField
id="name"
name="name"
onChange={handleNameInputChange}
value={revisionName}
/>
</InputWrapper>
<InputWrapper labelProps={{ label: "Created Date" }}>
<Typography variant="body_short">
{projectApiData?.createDate ? formatFullDate(projectApiData.createDate) : "N/A"}
</Typography>
</InputWrapper>
<InputWrapper labelProps={{ label: "Project Phase" }}>
<Typography variant="body_short">
{INTERNAL_PROJECT_PHASE[newInternalProjectPhase]?.label ?? "N/A"}
</Typography>
</InputWrapper>

<InputWrapper labelProps={{ label: "Project Classification" }}>
<Typography variant="body_short">
{newClassification ?? "N/A"}
</Typography>
</InputWrapper>
</ColumnWrapper>
</Grid>

<Grid item xs={12} md={8}>
<ColumnWrapper>
<Typography>Quality checks performed</Typography>
</ColumnWrapper>
<Wrapper>
<Chip disabled>
<Icon data={checkbox_outline} />
MDQC
</Chip>
<Chip disabled>
<Icon data={checkbox_outline} />
Arena
</Chip>
</Wrapper>
</Grid>
</DialogContent>
)}
actions={(
<div>
<Button variant="ghost" onClick={() => exitRevisionView()}>
<Icon data={exit_to_app} />
Exit revision
<DialogActions>
<Button onClick={closeMenu}>Close details</Button>
<Button
disabled={!isNameChanged}
onClick={handleRevisionNameChange}
>
Close and Save
</Button>
<Button onClick={() => closeMenu()}> Close details</Button>
</div>
</DialogActions>
)}
/>
)
}

export default CaseDropMenu
export default RevisionDetailsModal
7 changes: 4 additions & 3 deletions frontend/src/Components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ const RouteCoordinator = (): JSX.Element => {
const { setIsCreating, setIsLoading, setSnackBarMessage, isLoading } = useAppContext()
const { currentContext } = useModuleCurrentContext()

function isAxiosError(error: unknown): error is AxiosError {
return (error as AxiosError).isAxiosError !== undefined
}

const navigate = useNavigate()
const location = useLocation()

Expand Down Expand Up @@ -135,6 +139,3 @@ const RouteCoordinator = (): JSX.Element => {

export default RouteCoordinator

function isAxiosError(error: unknown): error is AxiosError {
return (error as AxiosError).isAxiosError !== undefined
}
5 changes: 5 additions & 0 deletions frontend/src/Services/ProjectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export class __ProjectService extends __BaseService {
return res
}

public async updateRevision(projectId: string, revisionId: string, body: Components.Schemas.UpdateProjectDto): Promise<Components.Schemas.ProjectWithAssetsDto> {
const res = await this.put(`${projectId}/revisions/${revisionId}`, { body })
return res
}

public async updateProject(projectId: string, body: Components.Schemas.UpdateProjectDto): Promise<Components.Schemas.ProjectWithAssetsDto> {
const res = await this.put(`${projectId}`, { body })
return res
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/Utils/RevisionUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ export const createRevision = async (
}
}

export const updateRevisionName = async (
projectId: string,
revisionId: string,
name: string,
) => {
const projectService = await GetProjectService()
const updateRevisionDto = { name }
const updatedRevision = await projectService.updateRevision(projectId, revisionId, updateRevisionDto)
return updatedRevision
}

export const navigateToRevision = (
revisionId: string,
setIsRevision: React.Dispatch<React.SetStateAction<boolean>>,
Expand Down
Loading