Merge branch 'develop' into dist/ubuntu/bionic/develop

This commit is contained in:
wpetit 2020-10-12 13:30:02 +02:00
commit 057a5c1fd0
10 changed files with 133 additions and 10 deletions

View File

@ -7,7 +7,7 @@ export interface DecisioSupportFileLinkProps {
decisionSupportFileId: number decisionSupportFileId: number
} }
export const DecisioSupportFileLink: FunctionComponent<DecisioSupportFileLinkProps> = ({ decisionSupportFileId }) => { export const DecisionSupportFileLink: FunctionComponent<DecisioSupportFileLinkProps> = ({ decisionSupportFileId }) => {
const { decisionSupportFiles } = useDecisionSupportFiles({ const { decisionSupportFiles } = useDecisionSupportFiles({
fetchPolicy: "cache-first", fetchPolicy: "cache-first",
variables: { variables: {

View File

@ -0,0 +1,51 @@
import React, { FunctionComponent, useState, ChangeEvent, useEffect } from 'react';
import { DecisionSupportFileUpdaterProps } from './DecisionSupportFileUpdaterProps';
export interface DecisionReportSectionProps extends DecisionSupportFileUpdaterProps {};
const DecisionReportSectionName = 'decision-report';
export const DecisionReportSection: FunctionComponent<DecisionReportSectionProps> = ({ dsf, updateDSF, readOnly }) => {
const [ state, setState ] = useState({
changed: false,
section: {
report: "",
}
});
useEffect(() => {
if (!state.changed) return;
updateDSF({ ...dsf, sections: { ...dsf.sections, [DecisionReportSectionName]: { ...state.section }} })
setState(state => ({ ...state, changed: false }));
}, [state.changed]);
useEffect(() => {
if (!dsf.sections[DecisionReportSectionName]) return;
setState(state => ({ ...state, changed: false, section: {...state.section, ...dsf.sections[DecisionReportSectionName] }}));
}, [dsf.sections[DecisionReportSectionName]]);
const onSectionAttrChange = (attrName: string, evt: ChangeEvent<HTMLInputElement>) => {
const target = evt.currentTarget;
const value = target.hasOwnProperty('checked') ? target.checked : target.value;
setState(state => ({ ...state, changed: true, section: {...state.section, [attrName]: value }}));
};
return (
<section>
<div className="box">
<div className="field">
<label className="label is-medium">Compte rendu du vote</label>
<div className="control">
<textarea className="textarea is-medium"
readOnly={readOnly}
value={state.section.report}
onChange={onSectionAttrChange.bind(null, 'report')}
rows={20}>
</textarea>
</div>
<p className="help is-info"><i className="fa fa-info-circle"></i> Penser à indiquer le résultat du vote et les éléments de contexte liés à la prise de décision.</p>
</div>
</div>
</section>
);
};

View File

@ -10,13 +10,14 @@ import { useCreateDecisionSupportFileMutation, useUpdateDecisionSupportFileMutat
import { OptionsSection } from './OptionsSection'; import { OptionsSection } from './OptionsSection';
import { useIsAuthorized } from '../../gql/queries/authorization'; import { useIsAuthorized } from '../../gql/queries/authorization';
import { TimelinePanel } from './TimelinePanel'; import { TimelinePanel } from './TimelinePanel';
import { DecisionReportSection } from './DecisionReportSection';
export interface DecisionSupportFilePageProps { export interface DecisionSupportFilePageProps {
}; };
export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => { export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageProps> = () => {
const { id } = useParams(); const { id } = useParams<any>();
const history = useHistory(); const history = useHistory();
const { decisionSupportFiles } = useDecisionSupportFiles({ const { decisionSupportFiles } = useDecisionSupportFiles({
variables:{ variables:{
@ -164,10 +165,15 @@ export const DecisionSupportFilePage: FunctionComponent<DecisionSupportFilePageP
<OptionsSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> : <OptionsSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
null null
} }
{
state.selectedTabIndex === 2 ?
<DecisionReportSection readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> :
null
}
</div> </div>
<div className="column is-4"> <div className="column is-4">
<MetadataPanel readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} /> <MetadataPanel readOnly={!isAuthorized} dsf={state.dsf} updateDSF={updateDSF} />
<AppendixPanel dsf={state.dsf} /> {/* <AppendixPanel dsf={state.dsf} /> */}
<TimelinePanel dsf={state.dsf} /> <TimelinePanel dsf={state.dsf} />
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@ import { formatDate } from "../util/date";
import { Event } from "../types/event"; import { Event } from "../types/event";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { WorkgroupLink } from "./WorkgroupLink"; import { WorkgroupLink } from "./WorkgroupLink";
import { DecisioSupportFileLink } from "./DecisionSupportFileLink"; import { DecisionSupportFileLink } from "./DecisionSupportFileLink";
export interface TimelineProps { export interface TimelineProps {
events?: Event[] events?: Event[]
@ -97,6 +97,11 @@ const eventMarkerMap = {
<i className="fas fa-users-slash"></i> <i className="fas fa-users-slash"></i>
</div> </div>
), ),
"voted": (evt:Event) => (
<div className="timeline-marker is-icon is-success">
<i className="fas fa-thumbs-up"></i>
</div>
),
} }
function renderEventMarker(evt: Event) { function renderEventMarker(evt: Event) {
@ -119,7 +124,7 @@ const eventContentMap = {
return ( return (
<React.Fragment> <React.Fragment>
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a créé le dossier d'aide à la décision `}</span> <span>{`${evt.user.name ? evt.user.name : evt.user.email} a créé le dossier d'aide à la décision `}</span>
"<DecisioSupportFileLink decisionSupportFileId={evt.objectId} />". "<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
</React.Fragment> </React.Fragment>
); );
}, },
@ -129,7 +134,7 @@ const eventContentMap = {
return ( return (
<React.Fragment> <React.Fragment>
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le titre du dossier d'aide à la décision `}</span> <span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le titre du dossier d'aide à la décision `}</span>
"<DecisioSupportFileLink decisionSupportFileId={evt.objectId} />". "<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
</React.Fragment> </React.Fragment>
) )
} }
@ -139,7 +144,7 @@ const eventContentMap = {
return ( return (
<React.Fragment> <React.Fragment>
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le statut du dossier d'aide à la décision `}</span> <span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le statut du dossier d'aide à la décision `}</span>
"<DecisioSupportFileLink decisionSupportFileId={evt.objectId} />". "<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
</React.Fragment> </React.Fragment>
) )
} }
@ -167,7 +172,7 @@ const eventContentMap = {
return ( return (
<React.Fragment> <React.Fragment>
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le dossier d'aide à la décision `}</span> <span>{`${evt.user.name ? evt.user.name : evt.user.email} a modifié le dossier d'aide à la décision `}</span>
"<DecisioSupportFileLink decisionSupportFileId={evt.objectId} />". "<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
</React.Fragment> </React.Fragment>
); );
}, },
@ -183,6 +188,14 @@ const eventContentMap = {
}, },
}, },
"closed": { "closed": {
"dsf": (evt:Event) => {
return (
<React.Fragment>
<span>{`${evt.user.name ? evt.user.name : evt.user.email} a clos le dossier d'aide à la décision `}</span>
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />".
</React.Fragment>
);
},
"workgroup": (evt:Event) => { "workgroup": (evt:Event) => {
return ( return (
<React.Fragment> <React.Fragment>
@ -192,6 +205,17 @@ const eventContentMap = {
); );
}, },
}, },
"voted": {
"dsf": (evt:Event) => {
return (
<React.Fragment>
<span>{`Le dossier d'aide à la décision `}</span>
"<DecisionSupportFileLink decisionSupportFileId={evt.objectId} />"
<span> a é voté.</span>
</React.Fragment>
);
},
},
}; };
function renderEventContent(evt: Event) { function renderEventContent(evt: Event) {

View File

@ -172,6 +172,16 @@ func NewDefault() *Config {
{{ end }} {{ end }}
{{- end}} {{- end}}
{{- with .Voted }}
Récemment votés
---------------
{{range . -}}
- "{{ .Title }}" - {{ $root.BaseURL }}/decisions/{{ .ID }} - voté le {{ .VotedAt.Format "02/01/2006" }}
{{ end }}
{{- end}}
{{- with .NewDecisionSupportFiles }} {{- with .NewDecisionSupportFiles }}
Nouveaux dossiers d'aide à la décision Nouveaux dossiers d'aide à la décision

View File

@ -72,10 +72,21 @@ func handleUpdateDecisionSupportFile(ctx context.Context, id string, changes *mo
eventRepo := model.NewEventRepository(db) eventRepo := model.NewEventRepository(db)
if changes != nil && changes.Status != nil && prevDsf.Status != *changes.Status { if changes != nil && changes.Status != nil && prevDsf.Status != *changes.Status {
switch *changes.Status {
case model.StatusVoted:
if _, err := eventRepo.Add(ctx, user, model.EventTypeVoted, dsf); err != nil {
return nil, errs.WithStack(err)
}
case model.StatusClosed:
if _, err := eventRepo.Add(ctx, user, model.EventTypeClosed, dsf); err != nil {
return nil, errs.WithStack(err)
}
default:
if _, err := eventRepo.Add(ctx, user, model.EventTypeStatusChanged, dsf); err != nil { if _, err := eventRepo.Add(ctx, user, model.EventTypeStatusChanged, dsf); err != nil {
return nil, errs.WithStack(err) return nil, errs.WithStack(err)
} }
} }
}
if changes != nil && changes.Title != nil && prevDsf.Title != *changes.Title { if changes != nil && changes.Title != nil && prevDsf.Title != *changes.Title {
if _, err := eventRepo.Add(ctx, user, model.EventTypeTitleChanged, dsf); err != nil { if _, err := eventRepo.Add(ctx, user, model.EventTypeTitleChanged, dsf); err != nil {

View File

@ -3,6 +3,7 @@ package model
import ( import (
"context" "context"
"errors" "errors"
"time"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
errs "github.com/pkg/errors" errs "github.com/pkg/errors"
@ -75,6 +76,14 @@ func (r *DSFRepository) updateFromChanges(dsf *DecisionSupportFile, changes *Dec
} }
if changes.Status != nil { if changes.Status != nil {
if dsf.Status != StatusVoted && *changes.Status == StatusVoted {
dsf.VotedAt = time.Now().UTC()
}
if *changes.Status == StatusClosed {
dsf.VotedAt = time.Time{}
}
dsf.Status = *changes.Status dsf.Status = *changes.Status
} }

View File

@ -31,6 +31,10 @@ func (v *DecisionSupportFileVoter) Vote(ctx context.Context, subject interface{}
case ActionRead: case ActionRead:
return voter.Allow, nil return voter.Allow, nil
case ActionUpdate: case ActionUpdate:
if dsf.Status == StatusClosed || dsf.Status == StatusVoted {
return voter.Deny, nil
}
if inWorkgroup(user, dsf.Workgroup) { if inWorkgroup(user, dsf.Workgroup) {
return voter.Allow, nil return voter.Allow, nil
} }

View File

@ -14,6 +14,7 @@ const (
EventTypeClosed EventType = "closed" EventTypeClosed EventType = "closed"
EventTypeStatusChanged EventType = "status-changed" EventTypeStatusChanged EventType = "status-changed"
EventTypeTitleChanged EventType = "title-changed" EventTypeTitleChanged EventType = "title-changed"
EventTypeVoted EventType = "voted"
) )
type EventObject interface { type EventObject interface {

View File

@ -73,6 +73,7 @@ func (t *Newsletter) Run() {
newWorkgroups := make([]*model.Workgroup, 0) newWorkgroups := make([]*model.Workgroup, 0)
newDecisionSupportFiles := make([]*model.DecisionSupportFile, 0) newDecisionSupportFiles := make([]*model.DecisionSupportFile, 0)
readyToVote := make([]*model.DecisionSupportFile, 0) readyToVote := make([]*model.DecisionSupportFile, 0)
voted := make([]*model.DecisionSupportFile, 0)
workgroupRepo := model.NewWorkgroupRepository(db) workgroupRepo := model.NewWorkgroupRepository(db)
dsfRepo := model.NewDSFRepository(db) dsfRepo := model.NewDSFRepository(db)
@ -122,6 +123,10 @@ func (t *Newsletter) Run() {
if dsf.Status == model.StatusReady { if dsf.Status == model.StatusReady {
readyToVote = append(readyToVote, dsf) readyToVote = append(readyToVote, dsf)
} }
if dsf.Status == model.StatusVoted {
voted = append(voted, dsf)
}
} }
} }
@ -147,6 +152,7 @@ func (t *Newsletter) Run() {
NewWorkgroups []*model.Workgroup NewWorkgroups []*model.Workgroup
NewDecisionSupportFiles []*model.DecisionSupportFile NewDecisionSupportFiles []*model.DecisionSupportFile
ReadyToVote []*model.DecisionSupportFile ReadyToVote []*model.DecisionSupportFile
Voted []*model.DecisionSupportFile
BaseURL string BaseURL string
From time.Time From time.Time
To time.Time To time.Time
@ -157,6 +163,7 @@ func (t *Newsletter) Run() {
NewWorkgroups: newWorkgroups, NewWorkgroups: newWorkgroups,
NewDecisionSupportFiles: newDecisionSupportFiles, NewDecisionSupportFiles: newDecisionSupportFiles,
ReadyToVote: readyToVote, ReadyToVote: readyToVote,
Voted: voted,
From: from.Local(), From: from.Local(),
To: to.Local(), To: to.Local(),
HasEvents: hasEvents, HasEvents: hasEvents,