feat: redesign due date manager

This commit is contained in:
Jordan Knott
2021-11-05 22:35:57 -05:00
parent df6140a10f
commit 0d00fc7518
34 changed files with 2204 additions and 196 deletions

View File

@ -187,6 +187,17 @@ type TaskComment struct {
Message string `json:"message"`
}
type TaskDueDateReminder struct {
DueDateReminderID uuid.UUID `json:"due_date_reminder_id"`
TaskID uuid.UUID `json:"task_id"`
Period int32 `json:"period"`
Duration string `json:"duration"`
}
type TaskDueDateReminderDuration struct {
Code string `json:"code"`
}
type TaskGroup struct {
TaskGroupID uuid.UUID `json:"task_group_id"`
ProjectID uuid.UUID `json:"project_id"`

View File

@ -12,6 +12,7 @@ import (
type Querier interface {
CreateAuthToken(ctx context.Context, arg CreateAuthTokenParams) (AuthToken, error)
CreateConfirmToken(ctx context.Context, email string) (UserAccountConfirmToken, error)
CreateDueDateReminder(ctx context.Context, arg CreateDueDateReminderParams) (TaskDueDateReminder, error)
CreateInvitedProjectMember(ctx context.Context, arg CreateInvitedProjectMemberParams) (ProjectMemberInvited, error)
CreateInvitedUser(ctx context.Context, email string) (UserAccountInvited, error)
CreateLabelColor(ctx context.Context, arg CreateLabelColorParams) (LabelColor, error)
@ -40,6 +41,7 @@ type Querier interface {
DeleteAuthTokenByID(ctx context.Context, tokenID uuid.UUID) error
DeleteAuthTokenByUserID(ctx context.Context, userID uuid.UUID) error
DeleteConfirmTokenForEmail(ctx context.Context, email string) error
DeleteDueDateReminder(ctx context.Context, dueDateReminderID uuid.UUID) error
DeleteExpiredTokens(ctx context.Context) error
DeleteInvitedProjectMemberByID(ctx context.Context, projectMemberInvitedID uuid.UUID) error
DeleteInvitedUserAccount(ctx context.Context, userAccountInvitedID uuid.UUID) (UserAccountInvited, error)
@ -80,6 +82,7 @@ type Querier interface {
GetCommentsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskComment, error)
GetConfirmTokenByEmail(ctx context.Context, email string) (UserAccountConfirmToken, error)
GetConfirmTokenByID(ctx context.Context, confirmTokenID uuid.UUID) (UserAccountConfirmToken, error)
GetDueDateRemindersForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskDueDateReminder, error)
GetInvitedMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]GetInvitedMembersForProjectIDRow, error)
GetInvitedUserAccounts(ctx context.Context) ([]UserAccountInvited, error)
GetInvitedUserByEmail(ctx context.Context, email string) (UserAccountInvited, error)
@ -150,6 +153,7 @@ type Querier interface {
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
SetUserActiveByEmail(ctx context.Context, email string) (UserAccount, error)
SetUserPassword(ctx context.Context, arg SetUserPasswordParams) (UserAccount, error)
UpdateDueDateReminder(ctx context.Context, arg UpdateDueDateReminderParams) (TaskDueDateReminder, error)
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)

View File

@ -116,3 +116,16 @@ SELECT task.* FROM task_assigned
-- name: GetCommentCountForTask :one
SELECT COUNT(*) FROM task_comment WHERE task_id = $1;
-- name: CreateDueDateReminder :one
INSERT INTO task_due_date_reminder (task_id, period, duration) VALUES ($1, $2, $3) RETURNING *;
-- name: UpdateDueDateReminder :one
UPDATE task_due_date_reminder SET period = $2, duration = $3 WHERE due_date_reminder_id = $1 RETURNING *;
-- name: GetDueDateRemindersForTaskID :many
SELECT * FROM task_due_date_reminder WHERE task_id = $1;
-- name: DeleteDueDateReminder :exec
DELETE FROM task_due_date_reminder WHERE due_date_reminder_id = $1;

View File

@ -12,6 +12,28 @@ import (
"github.com/lib/pq"
)
const createDueDateReminder = `-- name: CreateDueDateReminder :one
INSERT INTO task_due_date_reminder (task_id, period, duration) VALUES ($1, $2, $3) RETURNING due_date_reminder_id, task_id, period, duration
`
type CreateDueDateReminderParams struct {
TaskID uuid.UUID `json:"task_id"`
Period int32 `json:"period"`
Duration string `json:"duration"`
}
func (q *Queries) CreateDueDateReminder(ctx context.Context, arg CreateDueDateReminderParams) (TaskDueDateReminder, error) {
row := q.db.QueryRowContext(ctx, createDueDateReminder, arg.TaskID, arg.Period, arg.Duration)
var i TaskDueDateReminder
err := row.Scan(
&i.DueDateReminderID,
&i.TaskID,
&i.Period,
&i.Duration,
)
return i, err
}
const createTask = `-- name: CreateTask :one
INSERT INTO task (task_group_id, created_at, name, position)
VALUES($1, $2, $3, $4) RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time, short_id
@ -144,6 +166,15 @@ func (q *Queries) CreateTaskWatcher(ctx context.Context, arg CreateTaskWatcherPa
return i, err
}
const deleteDueDateReminder = `-- name: DeleteDueDateReminder :exec
DELETE FROM task_due_date_reminder WHERE due_date_reminder_id = $1
`
func (q *Queries) DeleteDueDateReminder(ctx context.Context, dueDateReminderID uuid.UUID) error {
_, err := q.db.ExecContext(ctx, deleteDueDateReminder, dueDateReminderID)
return err
}
const deleteTaskByID = `-- name: DeleteTaskByID :exec
DELETE FROM task WHERE task_id = $1
`
@ -403,6 +434,38 @@ func (q *Queries) GetCommentsForTaskID(ctx context.Context, taskID uuid.UUID) ([
return items, nil
}
const getDueDateRemindersForTaskID = `-- name: GetDueDateRemindersForTaskID :many
SELECT due_date_reminder_id, task_id, period, duration FROM task_due_date_reminder WHERE task_id = $1
`
func (q *Queries) GetDueDateRemindersForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskDueDateReminder, error) {
rows, err := q.db.QueryContext(ctx, getDueDateRemindersForTaskID, taskID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []TaskDueDateReminder
for rows.Next() {
var i TaskDueDateReminder
if err := rows.Scan(
&i.DueDateReminderID,
&i.TaskID,
&i.Period,
&i.Duration,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getProjectIDForTask = `-- name: GetProjectIDForTask :one
SELECT project_id FROM task
INNER JOIN task_group ON task_group.task_group_id = task.task_group_id
@ -651,6 +714,28 @@ func (q *Queries) SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams
return i, err
}
const updateDueDateReminder = `-- name: UpdateDueDateReminder :one
UPDATE task_due_date_reminder SET period = $2, duration = $3 WHERE due_date_reminder_id = $1 RETURNING due_date_reminder_id, task_id, period, duration
`
type UpdateDueDateReminderParams struct {
DueDateReminderID uuid.UUID `json:"due_date_reminder_id"`
Period int32 `json:"period"`
Duration string `json:"duration"`
}
func (q *Queries) UpdateDueDateReminder(ctx context.Context, arg UpdateDueDateReminderParams) (TaskDueDateReminder, error) {
row := q.db.QueryRowContext(ctx, updateDueDateReminder, arg.DueDateReminderID, arg.Period, arg.Duration)
var i TaskDueDateReminder
err := row.Scan(
&i.DueDateReminderID,
&i.TaskID,
&i.Period,
&i.Duration,
)
return i, err
}
const updateTaskComment = `-- name: UpdateTaskComment :one
UPDATE task_comment SET message = $2, updated_at = $3 WHERE task_comment_id = $1 RETURNING task_comment_id, task_id, created_at, updated_at, created_by, pinned, message
`

File diff suppressed because it is too large Load Diff

View File

@ -60,6 +60,16 @@ type CreateTaskCommentPayload struct {
Comment *db.TaskComment `json:"comment"`
}
type CreateTaskDueDateNotification struct {
TaskID uuid.UUID `json:"taskID"`
Period int `json:"period"`
Duration DueDateNotificationDuration `json:"duration"`
}
type CreateTaskDueDateNotificationsResult struct {
Notifications []DueDateNotification `json:"notifications"`
}
type CreateTeamMember struct {
UserID uuid.UUID `json:"userID"`
TeamID uuid.UUID `json:"teamID"`
@ -144,6 +154,14 @@ type DeleteTaskCommentPayload struct {
CommentID uuid.UUID `json:"commentID"`
}
type DeleteTaskDueDateNotification struct {
ID uuid.UUID `json:"id"`
}
type DeleteTaskDueDateNotificationsResult struct {
Notifications []uuid.UUID `json:"notifications"`
}
type DeleteTaskGroupInput struct {
TaskGroupID uuid.UUID `json:"taskGroupID"`
}
@ -203,6 +221,17 @@ type DeleteUserAccountPayload struct {
UserAccount *db.UserAccount `json:"userAccount"`
}
type DueDate struct {
At *time.Time `json:"at"`
Notifications []DueDateNotification `json:"notifications"`
}
type DueDateNotification struct {
ID uuid.UUID `json:"id"`
Period int `json:"period"`
Duration DueDateNotificationDuration `json:"duration"`
}
type DuplicateTaskGroup struct {
ProjectID uuid.UUID `json:"projectID"`
TaskGroupID uuid.UUID `json:"taskGroupID"`
@ -599,6 +628,16 @@ type UpdateTaskDueDate struct {
DueDate *time.Time `json:"dueDate"`
}
type UpdateTaskDueDateNotification struct {
ID uuid.UUID `json:"id"`
Period int `json:"period"`
Duration DueDateNotificationDuration `json:"duration"`
}
type UpdateTaskDueDateNotificationsResult struct {
Notifications []DueDateNotification `json:"notifications"`
}
type UpdateTaskGroupName struct {
TaskGroupID uuid.UUID `json:"taskGroupID"`
Name string `json:"name"`
@ -821,6 +860,51 @@ func (e ActivityType) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type DueDateNotificationDuration string
const (
DueDateNotificationDurationMinute DueDateNotificationDuration = "MINUTE"
DueDateNotificationDurationHour DueDateNotificationDuration = "HOUR"
DueDateNotificationDurationDay DueDateNotificationDuration = "DAY"
DueDateNotificationDurationWeek DueDateNotificationDuration = "WEEK"
)
var AllDueDateNotificationDuration = []DueDateNotificationDuration{
DueDateNotificationDurationMinute,
DueDateNotificationDurationHour,
DueDateNotificationDurationDay,
DueDateNotificationDurationWeek,
}
func (e DueDateNotificationDuration) IsValid() bool {
switch e {
case DueDateNotificationDurationMinute, DueDateNotificationDurationHour, DueDateNotificationDurationDay, DueDateNotificationDurationWeek:
return true
}
return false
}
func (e DueDateNotificationDuration) String() string {
return string(e)
}
func (e *DueDateNotificationDuration) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = DueDateNotificationDuration(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid DueDateNotificationDuration", str)
}
return nil
}
func (e DueDateNotificationDuration) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type MyTasksSort string
const (

View File

@ -1,3 +1,9 @@
enum DueDateNotificationDuration {
MINUTE
HOUR
DAY
WEEK
}
type TaskLabel {
id: ID!
@ -20,6 +26,18 @@ type TaskBadges {
comments: CommentsBadge
}
type DueDateNotification {
id: ID!
period: Int!
duration: DueDateNotificationDuration!
}
type DueDate {
at: Time
notifications: [DueDateNotification!]!
}
type Task {
id: ID!
shortId: String!
@ -29,7 +47,7 @@ type Task {
position: Float!
description: String
watched: Boolean!
dueDate: Time
dueDate: DueDate!
hasTime: Boolean!
complete: Boolean!
completedAt: Time
@ -371,8 +389,45 @@ extend type Mutation {
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
unassignTask(input: UnassignTaskInput):
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
createTaskDueDateNotifications(input: [CreateTaskDueDateNotification!]!):
CreateTaskDueDateNotificationsResult!
updateTaskDueDateNotifications(input: [UpdateTaskDueDateNotification!]!):
UpdateTaskDueDateNotificationsResult!
deleteTaskDueDateNotifications(input: [DeleteTaskDueDateNotification!]!):
DeleteTaskDueDateNotificationsResult!
}
input DeleteTaskDueDateNotification {
id: UUID!
}
type DeleteTaskDueDateNotificationsResult {
notifications: [UUID!]!
}
input UpdateTaskDueDateNotification {
id: UUID!
period: Int!
duration: DueDateNotificationDuration!
}
type UpdateTaskDueDateNotificationsResult {
notifications: [DueDateNotification!]!
}
input CreateTaskDueDateNotification {
taskID: UUID!
period: Int!
duration: DueDateNotificationDuration!
}
type CreateTaskDueDateNotificationsResult {
notifications: [DueDateNotification!]!
}
input ToggleTaskWatch {
taskID: UUID!
}

View File

@ -1,3 +1,9 @@
enum DueDateNotificationDuration {
MINUTE
HOUR
DAY
WEEK
}
type TaskLabel {
id: ID!
@ -20,6 +26,18 @@ type TaskBadges {
comments: CommentsBadge
}
type DueDateNotification {
id: ID!
period: Int!
duration: DueDateNotificationDuration!
}
type DueDate {
at: Time
notifications: [DueDateNotification!]!
}
type Task {
id: ID!
shortId: String!
@ -29,7 +47,7 @@ type Task {
position: Float!
description: String
watched: Boolean!
dueDate: Time
dueDate: DueDate!
hasTime: Boolean!
complete: Boolean!
completedAt: Time

View File

@ -31,8 +31,45 @@ extend type Mutation {
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
unassignTask(input: UnassignTaskInput):
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
createTaskDueDateNotifications(input: [CreateTaskDueDateNotification!]!):
CreateTaskDueDateNotificationsResult!
updateTaskDueDateNotifications(input: [UpdateTaskDueDateNotification!]!):
UpdateTaskDueDateNotificationsResult!
deleteTaskDueDateNotifications(input: [DeleteTaskDueDateNotification!]!):
DeleteTaskDueDateNotificationsResult!
}
input DeleteTaskDueDateNotification {
id: UUID!
}
type DeleteTaskDueDateNotificationsResult {
notifications: [UUID!]!
}
input UpdateTaskDueDateNotification {
id: UUID!
period: Int!
duration: DueDateNotificationDuration!
}
type UpdateTaskDueDateNotificationsResult {
notifications: [DueDateNotification!]!
}
input CreateTaskDueDateNotification {
taskID: UUID!
period: Int!
duration: DueDateNotificationDuration!
}
type CreateTaskDueDateNotificationsResult {
notifications: [DueDateNotification!]!
}
input ToggleTaskWatch {
taskID: UUID!
}

View File

@ -8,7 +8,7 @@ import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"strconv"
"time"
"github.com/google/uuid"
@ -501,8 +501,20 @@ func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTa
if err != nil {
return &db.Task{}, err
}
isSame := false
if prevTask.DueDate.Valid && input.DueDate != nil {
if prevTask.DueDate.Time == *input.DueDate && prevTask.HasTime == input.HasTime {
isSame = true
}
}
log.WithFields(log.Fields{
"isSame": isSame,
"prev": prevTask.HasTime,
"new": input.HasTime,
}).Info("chekcing same")
data := map[string]string{}
var activityType = TASK_DUE_DATE_ADDED
data["HasTime"] = strconv.FormatBool(input.HasTime)
if input.DueDate == nil && prevTask.DueDate.Valid {
activityType = TASK_DUE_DATE_REMOVED
data["PrevDueDate"] = prevTask.DueDate.Time.String()
@ -529,13 +541,15 @@ func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTa
})
createdAt := time.Now().UTC()
d, _ := json.Marshal(data)
_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
TaskID: task.TaskID,
Data: d,
CausedBy: userID,
CreatedAt: createdAt,
ActivityTypeID: activityType,
})
if !isSame {
_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
TaskID: task.TaskID,
Data: d,
CausedBy: userID,
CreatedAt: createdAt,
ActivityTypeID: activityType,
})
}
} else {
task, err = r.Repository.GetTaskByID(ctx, input.TaskID)
}
@ -670,6 +684,73 @@ func (r *mutationResolver) UnassignTask(ctx context.Context, input *UnassignTask
return &task, nil
}
func (r *mutationResolver) CreateTaskDueDateNotifications(ctx context.Context, input []CreateTaskDueDateNotification) (*CreateTaskDueDateNotificationsResult, error) {
reminders := []DueDateNotification{}
for _, in := range input {
n, err := r.Repository.CreateDueDateReminder(ctx, db.CreateDueDateReminderParams{
TaskID: in.TaskID,
Period: int32(in.Period),
Duration: in.Duration.String(),
})
if err != nil {
return &CreateTaskDueDateNotificationsResult{}, err
}
duration := DueDateNotificationDuration(n.Duration)
if !duration.IsValid() {
log.WithField("duration", n.Duration).Error("invalid duration found")
return &CreateTaskDueDateNotificationsResult{}, errors.New("invalid duration")
}
reminders = append(reminders, DueDateNotification{
ID: n.DueDateReminderID,
Period: int(n.Period),
Duration: duration,
})
}
return &CreateTaskDueDateNotificationsResult{
Notifications: reminders,
}, nil
}
func (r *mutationResolver) UpdateTaskDueDateNotifications(ctx context.Context, input []UpdateTaskDueDateNotification) (*UpdateTaskDueDateNotificationsResult, error) {
reminders := []DueDateNotification{}
for _, in := range input {
n, err := r.Repository.UpdateDueDateReminder(ctx, db.UpdateDueDateReminderParams{
DueDateReminderID: in.ID,
Period: int32(in.Period),
Duration: in.Duration.String(),
})
if err != nil {
return &UpdateTaskDueDateNotificationsResult{}, err
}
duration := DueDateNotificationDuration(n.Duration)
if !duration.IsValid() {
log.WithField("duration", n.Duration).Error("invalid duration found")
return &UpdateTaskDueDateNotificationsResult{}, errors.New("invalid duration")
}
reminders = append(reminders, DueDateNotification{
ID: n.DueDateReminderID,
Period: int(n.Period),
Duration: duration,
})
}
return &UpdateTaskDueDateNotificationsResult{
Notifications: reminders,
}, nil
}
func (r *mutationResolver) DeleteTaskDueDateNotifications(ctx context.Context, input []DeleteTaskDueDateNotification) (*DeleteTaskDueDateNotificationsResult, error) {
ids := []uuid.UUID{}
for _, n := range input {
err := r.Repository.DeleteDueDateReminder(ctx, n.ID)
if err != nil {
log.WithError(err).Error("error while deleting task due date notification")
return &DeleteTaskDueDateNotificationsResult{}, err
}
ids = append(ids, n.ID)
}
return &DeleteTaskDueDateNotificationsResult{Notifications: ids}, nil
}
func (r *queryResolver) FindTask(ctx context.Context, input FindTask) (*db.Task, error) {
var taskID uuid.UUID
var err error
@ -724,11 +805,34 @@ func (r *taskResolver) Watched(ctx context.Context, obj *db.Task) (bool, error)
return true, nil
}
func (r *taskResolver) DueDate(ctx context.Context, obj *db.Task) (*time.Time, error) {
if obj.DueDate.Valid {
return &obj.DueDate.Time, nil
func (r *taskResolver) DueDate(ctx context.Context, obj *db.Task) (*DueDate, error) {
nots, err := r.Repository.GetDueDateRemindersForTaskID(ctx, obj.TaskID)
if err != nil {
log.WithError(err).Error("error while fetching due date reminders")
return &DueDate{}, err
}
return nil, nil
reminders := []DueDateNotification{}
for _, n := range nots {
duration := DueDateNotificationDuration(n.Duration)
if !duration.IsValid() {
log.WithField("duration", n.Duration).Error("invalid duration found")
return &DueDate{}, errors.New("invalid duration")
}
reminders = append(reminders, DueDateNotification{
ID: n.DueDateReminderID,
Period: int(n.Period),
Duration: duration,
})
}
var time *time.Time
if obj.DueDate.Valid {
time = &obj.DueDate.Time
}
return &DueDate{
At: time,
Notifications: reminders,
}, nil
}
func (r *taskResolver) CompletedAt(ctx context.Context, obj *db.Task) (*time.Time, error) {
@ -904,7 +1008,10 @@ func (r *taskChecklistItemResolver) ID(ctx context.Context, obj *db.TaskChecklis
}
func (r *taskChecklistItemResolver) DueDate(ctx context.Context, obj *db.TaskChecklistItem) (*time.Time, error) {
panic(fmt.Errorf("not implemented"))
if obj.DueDate.Valid {
return &obj.DueDate.Time, nil
}
return nil, nil
}
func (r *taskCommentResolver) ID(ctx context.Context, obj *db.TaskComment) (uuid.UUID, error) {