From 833213e5fe1b56e2495371ab6daf369a846cedbd Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 11 Sep 2020 12:41:07 +0200 Subject: [PATCH] feat(ui+backend): project params persistence --- client/src/gql/fragments/project.ts | 19 ++++--- client/src/gql/mutations/project.tsx | 15 +++++- client/src/hooks/useProjectReducer.sagas.ts | 31 ++++++++++- client/src/hooks/useProjectReducer.ts | 12 +++++ internal/graph/mutation.graphql | 13 +++++ internal/graph/mutation.resolvers.go | 4 ++ internal/graph/project_handler.go | 16 ++++++ internal/graph/query.resolvers.go | 8 --- internal/model/project_repository.go | 60 +++++++++++++++++++++ 9 files changed, 161 insertions(+), 17 deletions(-) diff --git a/client/src/gql/fragments/project.ts b/client/src/gql/fragments/project.ts index 64a3b78..d1174f8 100644 --- a/client/src/gql/fragments/project.ts +++ b/client/src/gql/fragments/project.ts @@ -16,6 +16,17 @@ fragment FullTask on Task { } `; +export const FRAGMENT_FULL_PROJECT_PARAMS = gql` +fragment FullProjectParams on ProjectParams { + timeUnit { + label + acronym + } + currency + hideFinancialPreviewOnPrint +} +`; + export const FRAGMENT_FULL_PROJECT = gql` fragment FullProject on Project { id @@ -29,13 +40,9 @@ fragment FullProject on Project { ...FullTask } params { - timeUnit { - label - acronym - } - currency - hideFinancialPreviewOnPrint + ...FullProjectParams } } ${FRAGMENT_FULL_TASK} +${FRAGMENT_FULL_PROJECT_PARAMS} ` \ No newline at end of file diff --git a/client/src/gql/mutations/project.tsx b/client/src/gql/mutations/project.tsx index af0ace3..536e064 100644 --- a/client/src/gql/mutations/project.tsx +++ b/client/src/gql/mutations/project.tsx @@ -1,5 +1,5 @@ import { gql, useMutation, PureQueryOptions } from '@apollo/client'; -import { FRAGMENT_FULL_PROJECT, FRAGMENT_FULL_TASK } from '../fragments/project'; +import { FRAGMENT_FULL_PROJECT, FRAGMENT_FULL_TASK, FRAGMENT_FULL_PROJECT_PARAMS } from '../fragments/project'; import { QUERY_PROJECTS } from '../queries/project'; export const MUTATION_CREATE_PROJECT = gql` @@ -69,3 +69,16 @@ mutation removeProjectTask($projectId: ID!, $taskId: ID!) { export function useRemoveProjectTaskMutation() { return useMutation(MUTATION_REMOVE_PROJECT_TASK); } + +export const MUTATION_UPDATE_PROJECT_PARAMS = gql` +mutation updateProjectParams($projectId: ID!, $changes: ProjectParamsChanges!) { + updateProjectParams(projectId: $projectId, changes: $changes) { + ...FullProjectParams + } +} +${FRAGMENT_FULL_PROJECT_PARAMS} +`; + +export function useUpdateProjectParamsMutation() { + return useMutation(MUTATION_UPDATE_PROJECT_PARAMS); +} diff --git a/client/src/hooks/useProjectReducer.sagas.ts b/client/src/hooks/useProjectReducer.sagas.ts index 7c86995..b50b62c 100644 --- a/client/src/hooks/useProjectReducer.sagas.ts +++ b/client/src/hooks/useProjectReducer.sagas.ts @@ -1,7 +1,7 @@ import { all, select, takeLatest, put, delay } from "redux-saga/effects"; import { client } from '../gql/client'; -import { MUTATION_CREATE_PROJECT, MUTATION_UPDATE_PROJECT_TITLE, MUTATION_ADD_PROJECT_TASK, MUTATION_REMOVE_PROJECT_TASK, MUTATION_UPDATE_PROJECT_TASK } from "../gql/mutations/project"; -import { UPDATE_PROJECT_TITLE, resetProject, ADD_TASK, taskSaved, AddTask, taskRemoved, RemoveTask, REMOVE_TASK, UPDATE_TASK_ESTIMATION, updateTaskEstimation, UpdateTaskEstimation, UpdateTaskLabel, UPDATE_TASK_LABEL } from "./useProjectReducer"; +import { MUTATION_CREATE_PROJECT, MUTATION_UPDATE_PROJECT_TITLE, MUTATION_ADD_PROJECT_TASK, MUTATION_REMOVE_PROJECT_TASK, MUTATION_UPDATE_PROJECT_TASK, MUTATION_UPDATE_PROJECT_PARAMS } from "../gql/mutations/project"; +import { UPDATE_PROJECT_TITLE, resetProject, ADD_TASK, taskSaved, AddTask, taskRemoved, RemoveTask, REMOVE_TASK, UPDATE_TASK_ESTIMATION, updateTaskEstimation, UpdateTaskEstimation, UpdateTaskLabel, UPDATE_TASK_LABEL, UpdateParam, paramsSaved, UPDATE_PARAM } from "./useProjectReducer"; import { Project } from "../types/project"; export function* rootSaga() { @@ -10,6 +10,7 @@ export function* rootSaga() { takeLatest(UPDATE_PROJECT_TITLE, updateProjectTitleSaga), takeLatest(UPDATE_TASK_ESTIMATION, updateTaskEstimationSaga), takeLatest(UPDATE_TASK_LABEL, updateTaskLabelSaga), + takeLatest(UPDATE_PARAM, updateProjectParamsSaga), takeLatest(ADD_TASK, addTaskSaga), takeLatest(REMOVE_TASK, removeTaskSaga), ]); @@ -136,4 +137,30 @@ export function* updateTaskLabelSaga({ id, label }: UpdateTaskLabel) { }); yield put(taskSaved({ ...data.updateProjectTask })); +} + +export function* updateProjectParamsSaga({ name, value }: UpdateParam) { + yield delay(500); + + let project: Project = yield select(); + + if (project.id === undefined) { + project = yield createProjectSaga(); + } + + if (typeof value === 'object') { + delete value.__typename; + } + + const { data } = yield client.mutate({ + mutation: MUTATION_UPDATE_PROJECT_PARAMS, + variables: { + projectId: project.id, + changes: { + [name]: value, + } + } + }); + + yield put(paramsSaved({ ...data.updateProjectParams })); } \ No newline at end of file diff --git a/client/src/hooks/useProjectReducer.ts b/client/src/hooks/useProjectReducer.ts index 7877a9a..b8f6fc3 100644 --- a/client/src/hooks/useProjectReducer.ts +++ b/client/src/hooks/useProjectReducer.ts @@ -3,6 +3,7 @@ import { Task, TaskID, EstimationConfidence, TaskCategoryID, TaskCategory } from import { useReducerAndSaga } from "./useReducerAndSaga"; import { rootSaga } from "./useProjectReducer.sagas"; import { uuid } from "../util/uuid"; +import { Params } from "../types/params"; export interface Action { type: string @@ -17,6 +18,7 @@ UpdateTaskEstimation | UpdateProjectTitle | UpdateTaskLabel | UpdateParam | +ParamsSaved | UpdateTaskCategoryLabel | UpdateTaskCategoryCost | AddTaskCategory | @@ -232,6 +234,16 @@ export function handleUpdateParam(project: Project, action: UpdateParam): Projec }; } +export interface ParamsSaved extends Action { + params: Params +} + +export const PARAMS_SAVED = "PARAMS_SAVED"; + +export function paramsSaved(params: Params): ParamsSaved { + return { type: PARAMS_SAVED, params }; +} + export interface UpdateTaskCategoryLabel extends Action { categoryId: TaskCategoryID label: string diff --git a/internal/graph/mutation.graphql b/internal/graph/mutation.graphql index 4a1cf91..24ee278 100644 --- a/internal/graph/mutation.graphql +++ b/internal/graph/mutation.graphql @@ -18,6 +18,18 @@ input ProjectTaskEstimationsChanges { pessimistic: Float } +input ProjectParamsChanges { + timeUnit: TimeUnitChanges + currency: String + roundUpEstimations: Boolean + hideFinancialPreviewOnPrint: Boolean +} + +input TimeUnitChanges { + label: String + acronym: String +} + type Mutation { updateUser(id: ID!, changes: UserChanges!): User! createProject(changes: CreateProjectChanges!): Project! @@ -25,4 +37,5 @@ type Mutation { addProjectTask(projectId: ID!, changes: ProjectTaskChanges!): Task! removeProjectTask(projectId: ID!, taskId: ID!): Boolean! updateProjectTask(projectId: ID!, taskId: ID!, changes: ProjectTaskChanges!): Task! + updateProjectParams(projectId: ID!, changes: ProjectParamsChanges!): ProjectParams! } \ No newline at end of file diff --git a/internal/graph/mutation.resolvers.go b/internal/graph/mutation.resolvers.go index 02b5882..87ea0f6 100644 --- a/internal/graph/mutation.resolvers.go +++ b/internal/graph/mutation.resolvers.go @@ -34,6 +34,10 @@ func (r *mutationResolver) UpdateProjectTask(ctx context.Context, projectID int6 return handleUpdateProjectTask(ctx, projectID, taskID, changes) } +func (r *mutationResolver) UpdateProjectParams(ctx context.Context, projectID int64, changes model.ProjectParamsChanges) (*model.ProjectParams, error) { + return handleUpdateProjectParams(ctx, projectID, changes) +} + // Mutation returns generated.MutationResolver implementation. func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } diff --git a/internal/graph/project_handler.go b/internal/graph/project_handler.go index d2e95e9..2743cad 100644 --- a/internal/graph/project_handler.go +++ b/internal/graph/project_handler.go @@ -110,3 +110,19 @@ func handleUpdateProjectTask(ctx context.Context, projectID, taskID int64, chang return task, nil } + +func handleUpdateProjectParams(ctx context.Context, projectID int64, changes model.ProjectParamsChanges) (*model.ProjectParams, error) { + db, err := getDB(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + repo := model.NewProjectRepository(db) + + project, err := repo.UpdateParams(ctx, projectID, changes) + if err != nil { + return nil, errors.WithStack(err) + } + + return project.Params, nil +} diff --git a/internal/graph/query.resolvers.go b/internal/graph/query.resolvers.go index 73073dc..7c2bf2f 100644 --- a/internal/graph/query.resolvers.go +++ b/internal/graph/query.resolvers.go @@ -18,15 +18,7 @@ func (r *queryResolver) Projects(ctx context.Context, filter *model1.ProjectsFil return handleProjects(ctx, filter) } -func (r *taskResolver) Estimations(ctx context.Context, obj *model1.Task) (*model1.Estimations, error) { - return handleEstimations(ctx, obj) -} - // Query returns generated.QueryResolver implementation. func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } -// Task returns generated.TaskResolver implementation. -func (r *Resolver) Task() generated.TaskResolver { return &taskResolver{r} } - type queryResolver struct{ *Resolver } -type taskResolver struct{ *Resolver } diff --git a/internal/model/project_repository.go b/internal/model/project_repository.go index cf75200..569a133 100644 --- a/internal/model/project_repository.go +++ b/internal/model/project_repository.go @@ -219,6 +219,66 @@ func (r *ProjectRepository) UpdateTask(ctx context.Context, projectID, taskID in return task, nil } +func (r *ProjectRepository) UpdateParams(ctx context.Context, projectID int64, changes ProjectParamsChanges) (*Project, error) { + project := &Project{} + project.ID = projectID + + err := r.db.Transaction(func(tx *gorm.DB) error { + err := tx.Model(project). + Preload("ACL"). + Preload("ACL.User"). + Preload("Tasks"). + Preload("Tasks.Category"). + Preload("TaskCategories"). + First(project, "id = ?", projectID). + Error + if err != nil { + return errors.WithStack(err) + } + + if project.Params == nil { + project.Params = &ProjectParams{} + } + + if changes.Currency != nil { + project.Params.Currency = *changes.Currency + } + + if changes.HideFinancialPreviewOnPrint != nil { + project.Params.HideFinancialPreviewOnPrint = *changes.HideFinancialPreviewOnPrint + } + + if changes.RoundUpEstimations != nil { + project.Params.RoundUpEstimations = *changes.RoundUpEstimations + } + + if changes.TimeUnit != nil { + if project.Params.TimeUnit == nil { + project.Params.TimeUnit = &TimeUnit{} + } + + if changes.TimeUnit.Acronym != nil { + project.Params.TimeUnit.Acronym = *changes.TimeUnit.Acronym + } + + if changes.TimeUnit.Label != nil { + project.Params.TimeUnit.Label = *changes.TimeUnit.Label + } + } + + if err := tx.Save(project).Error; err != nil { + return errors.WithStack(err) + } + + return nil + }) + if err != nil { + return nil, errors.Wrap(err, "could not update project params") + } + + return project, nil +} + func updateTaskWithChanges(db *gorm.DB, task *Task, changes ProjectTaskChanges) error { if changes.Label != nil { task.Label = changes.Label