feat(ui+backend): project params persistence
This commit is contained in:
parent
aacff1d694
commit
833213e5fe
|
@ -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`
|
export const FRAGMENT_FULL_PROJECT = gql`
|
||||||
fragment FullProject on Project {
|
fragment FullProject on Project {
|
||||||
id
|
id
|
||||||
|
@ -29,13 +40,9 @@ fragment FullProject on Project {
|
||||||
...FullTask
|
...FullTask
|
||||||
}
|
}
|
||||||
params {
|
params {
|
||||||
timeUnit {
|
...FullProjectParams
|
||||||
label
|
|
||||||
acronym
|
|
||||||
}
|
|
||||||
currency
|
|
||||||
hideFinancialPreviewOnPrint
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${FRAGMENT_FULL_TASK}
|
${FRAGMENT_FULL_TASK}
|
||||||
|
${FRAGMENT_FULL_PROJECT_PARAMS}
|
||||||
`
|
`
|
|
@ -1,5 +1,5 @@
|
||||||
import { gql, useMutation, PureQueryOptions } from '@apollo/client';
|
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';
|
import { QUERY_PROJECTS } from '../queries/project';
|
||||||
|
|
||||||
export const MUTATION_CREATE_PROJECT = gql`
|
export const MUTATION_CREATE_PROJECT = gql`
|
||||||
|
@ -69,3 +69,16 @@ mutation removeProjectTask($projectId: ID!, $taskId: ID!) {
|
||||||
export function useRemoveProjectTaskMutation() {
|
export function useRemoveProjectTaskMutation() {
|
||||||
return useMutation(MUTATION_REMOVE_PROJECT_TASK);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { all, select, takeLatest, put, delay } from "redux-saga/effects";
|
import { all, select, takeLatest, put, delay } from "redux-saga/effects";
|
||||||
import { client } from '../gql/client';
|
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 { 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 } from "./useProjectReducer";
|
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";
|
import { Project } from "../types/project";
|
||||||
|
|
||||||
export function* rootSaga() {
|
export function* rootSaga() {
|
||||||
|
@ -10,6 +10,7 @@ export function* rootSaga() {
|
||||||
takeLatest(UPDATE_PROJECT_TITLE, updateProjectTitleSaga),
|
takeLatest(UPDATE_PROJECT_TITLE, updateProjectTitleSaga),
|
||||||
takeLatest(UPDATE_TASK_ESTIMATION, updateTaskEstimationSaga),
|
takeLatest(UPDATE_TASK_ESTIMATION, updateTaskEstimationSaga),
|
||||||
takeLatest(UPDATE_TASK_LABEL, updateTaskLabelSaga),
|
takeLatest(UPDATE_TASK_LABEL, updateTaskLabelSaga),
|
||||||
|
takeLatest(UPDATE_PARAM, updateProjectParamsSaga),
|
||||||
takeLatest(ADD_TASK, addTaskSaga),
|
takeLatest(ADD_TASK, addTaskSaga),
|
||||||
takeLatest(REMOVE_TASK, removeTaskSaga),
|
takeLatest(REMOVE_TASK, removeTaskSaga),
|
||||||
]);
|
]);
|
||||||
|
@ -137,3 +138,29 @@ export function* updateTaskLabelSaga({ id, label }: UpdateTaskLabel) {
|
||||||
|
|
||||||
yield put(taskSaved({ ...data.updateProjectTask }));
|
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 }));
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import { Task, TaskID, EstimationConfidence, TaskCategoryID, TaskCategory } from
|
||||||
import { useReducerAndSaga } from "./useReducerAndSaga";
|
import { useReducerAndSaga } from "./useReducerAndSaga";
|
||||||
import { rootSaga } from "./useProjectReducer.sagas";
|
import { rootSaga } from "./useProjectReducer.sagas";
|
||||||
import { uuid } from "../util/uuid";
|
import { uuid } from "../util/uuid";
|
||||||
|
import { Params } from "../types/params";
|
||||||
|
|
||||||
export interface Action {
|
export interface Action {
|
||||||
type: string
|
type: string
|
||||||
|
@ -17,6 +18,7 @@ UpdateTaskEstimation |
|
||||||
UpdateProjectTitle |
|
UpdateProjectTitle |
|
||||||
UpdateTaskLabel |
|
UpdateTaskLabel |
|
||||||
UpdateParam |
|
UpdateParam |
|
||||||
|
ParamsSaved |
|
||||||
UpdateTaskCategoryLabel |
|
UpdateTaskCategoryLabel |
|
||||||
UpdateTaskCategoryCost |
|
UpdateTaskCategoryCost |
|
||||||
AddTaskCategory |
|
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 {
|
export interface UpdateTaskCategoryLabel extends Action {
|
||||||
categoryId: TaskCategoryID
|
categoryId: TaskCategoryID
|
||||||
label: string
|
label: string
|
||||||
|
|
|
@ -18,6 +18,18 @@ input ProjectTaskEstimationsChanges {
|
||||||
pessimistic: Float
|
pessimistic: Float
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input ProjectParamsChanges {
|
||||||
|
timeUnit: TimeUnitChanges
|
||||||
|
currency: String
|
||||||
|
roundUpEstimations: Boolean
|
||||||
|
hideFinancialPreviewOnPrint: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input TimeUnitChanges {
|
||||||
|
label: String
|
||||||
|
acronym: String
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
updateUser(id: ID!, changes: UserChanges!): User!
|
updateUser(id: ID!, changes: UserChanges!): User!
|
||||||
createProject(changes: CreateProjectChanges!): Project!
|
createProject(changes: CreateProjectChanges!): Project!
|
||||||
|
@ -25,4 +37,5 @@ type Mutation {
|
||||||
addProjectTask(projectId: ID!, changes: ProjectTaskChanges!): Task!
|
addProjectTask(projectId: ID!, changes: ProjectTaskChanges!): Task!
|
||||||
removeProjectTask(projectId: ID!, taskId: ID!): Boolean!
|
removeProjectTask(projectId: ID!, taskId: ID!): Boolean!
|
||||||
updateProjectTask(projectId: ID!, taskId: ID!, changes: ProjectTaskChanges!): Task!
|
updateProjectTask(projectId: ID!, taskId: ID!, changes: ProjectTaskChanges!): Task!
|
||||||
|
updateProjectParams(projectId: ID!, changes: ProjectParamsChanges!): ProjectParams!
|
||||||
}
|
}
|
|
@ -34,6 +34,10 @@ func (r *mutationResolver) UpdateProjectTask(ctx context.Context, projectID int6
|
||||||
return handleUpdateProjectTask(ctx, projectID, taskID, changes)
|
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.
|
// Mutation returns generated.MutationResolver implementation.
|
||||||
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
|
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
|
||||||
|
|
||||||
|
|
|
@ -110,3 +110,19 @@ func handleUpdateProjectTask(ctx context.Context, projectID, taskID int64, chang
|
||||||
|
|
||||||
return task, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -18,15 +18,7 @@ func (r *queryResolver) Projects(ctx context.Context, filter *model1.ProjectsFil
|
||||||
return handleProjects(ctx, filter)
|
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.
|
// Query returns generated.QueryResolver implementation.
|
||||||
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
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 queryResolver struct{ *Resolver }
|
||||||
type taskResolver struct{ *Resolver }
|
|
||||||
|
|
|
@ -219,6 +219,66 @@ func (r *ProjectRepository) UpdateTask(ctx context.Context, projectID, taskID in
|
||||||
return task, nil
|
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 {
|
func updateTaskWithChanges(db *gorm.DB, task *Task, changes ProjectTaskChanges) error {
|
||||||
if changes.Label != nil {
|
if changes.Label != nil {
|
||||||
task.Label = changes.Label
|
task.Label = changes.Label
|
||||||
|
|
Loading…
Reference in New Issue