Browse Source

add task and item filters to goals.

main
Gisle Aune 3 years ago
parent
commit
a187e91c3c
  1. 8
      database/postgres/goals.go
  2. 13
      migrations/postgres/20210119211214_add_goal_columns_filters.sql
  3. 46
      models/goal.go
  4. 26
      services/loader.go
  5. 19
      svelte-ui/src/components/Composition.svelte
  6. 14
      svelte-ui/src/forms/GoalForm.svelte
  7. 15
      svelte-ui/src/models/goal.ts
  8. 2
      svelte-ui/src/models/log.ts

8
database/postgres/goals.go

@ -74,10 +74,10 @@ func (r *goalRepository) Insert(ctx context.Context, goal models.Goal) error {
_, err := r.db.NamedExecContext(ctx, ` _, err := r.db.NamedExecContext(ctx, `
INSERT INTO goal ( INSERT INTO goal (
goal_id, user_id, group_id, amount, start_time, end_time, name, description, goal_id, user_id, group_id, amount, start_time, end_time, name, description,
composition_mode, unweighted, item_id
composition_mode, unweighted, item_id, task_filter, item_filter
) VALUES ( ) VALUES (
:goal_id, :user_id, :group_id, :amount, :start_time, :end_time, :name, :description, :goal_id, :user_id, :group_id, :amount, :start_time, :end_time, :name, :description,
:composition_mode, :unweighted, :item_id
:composition_mode, :unweighted, :item_id, :task_filter, :item_filter
) )
`, &goal) `, &goal)
if err != nil { if err != nil {
@ -97,7 +97,9 @@ func (r *goalRepository) Update(ctx context.Context, goal models.Goal) error {
description=:description, description=:description,
composition_mode=:composition_mode, composition_mode=:composition_mode,
unweighted=:unweighted, unweighted=:unweighted,
item_id=:item_id
item_id=:item_id,
task_filter=:task_filter,
item_filter=:item_filter
WHERE goal_id=:goal_id WHERE goal_id=:goal_id
`, &goal) `, &goal)
if err != nil { if err != nil {

13
migrations/postgres/20210119211214_add_goal_columns_filters.sql

@ -0,0 +1,13 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE goal
ADD COLUMN task_filter TEXT DEFAULT NULL,
ADD COLUMN item_filter TEXT DEFAULT NULL;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
ALTER TABLE goal
DROP COLUMN task_filter,
DROP COLUMN item_filter;
-- +goose StatementEnd

46
models/goal.go

@ -2,6 +2,7 @@ package models
import ( import (
"context" "context"
"strings"
"time" "time"
) )
@ -17,6 +18,25 @@ type Goal struct {
Name string `json:"name" db:"name"` Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"` Description string `json:"description" db:"description"`
CompositionMode string `json:"compositionMode" db:"composition_mode"` CompositionMode string `json:"compositionMode" db:"composition_mode"`
TaskFilter *string `json:"taskFilter" db:"task_filter"`
ItemFilter *string `json:"itemFilter" db:"item_filter"`
}
func (goal *Goal) Accepts(item *Item, task *Task) bool {
if item.GroupID != goal.GroupID {
return false
}
if goal.ItemID != nil && item.ID != *goal.ItemID {
return false
}
if goal.TaskFilter != nil && !strings.Contains(strings.ToLower(task.Name), *goal.TaskFilter) {
return false
}
if goal.ItemFilter != nil && !strings.Contains(strings.ToLower(item.Name), *goal.ItemFilter) {
return false
}
return true
} }
func (goal *Goal) Update(update GoalUpdate) { func (goal *Goal) Update(update GoalUpdate) {
@ -47,6 +67,18 @@ func (goal *Goal) Update(update GoalUpdate) {
if update.ClearItemID { if update.ClearItemID {
goal.ItemID = nil goal.ItemID = nil
} }
if update.TaskFilter != nil {
goal.TaskFilter = update.TaskFilter
}
if update.ClearTaskFilter {
goal.TaskFilter = nil
}
if update.ItemFilter != nil {
goal.ItemFilter = update.ItemFilter
}
if update.ClearItemFilter {
goal.ItemFilter = nil
}
} }
type GoalUpdate struct { type GoalUpdate struct {
@ -56,19 +88,29 @@ type GoalUpdate struct {
Name *string `json:"name"` Name *string `json:"name"`
Description *string `json:"description"` Description *string `json:"description"`
ItemID *string `json:"itemId"` ItemID *string `json:"itemId"`
ClearItemID bool `json:"clearItemID"`
Unweighted *bool `json:"unweighted"` Unweighted *bool `json:"unweighted"`
CompositionMode *string `json:"compositionMode"` CompositionMode *string `json:"compositionMode"`
ClearItemID bool `json:"clearItemID"`
TaskFilter *string `json:"taskFilter"`
ClearTaskFilter bool `json:"clearTaskFilter"`
ItemFilter *string `json:"itemFilter"`
ClearItemFilter bool `json:"clearItemFilter"`
} }
type GoalResult struct { type GoalResult struct {
Goal Goal
Group *Group `json:"group"` Group *Group `json:"group"`
Items []*GoalResultItem `json:"items"` Items []*GoalResultItem `json:"items"`
Logs []*LogResult `json:"logs"`
Logs []*GoalResultLog `json:"logs"`
CompletedAmount int `json:"completedAmount"` CompletedAmount int `json:"completedAmount"`
} }
type GoalResultLog struct {
LogResult
ItemCounted bool `json:"itemCounted"`
SecondaryItemCounted bool `json:"secondaryItemCounted"`
}
type GoalResultItem struct { type GoalResultItem struct {
Item Item
CompletedAmount int `json:"completedAmount"` CompletedAmount int `json:"completedAmount"`

26
services/loader.go

@ -535,11 +535,12 @@ func (l *Loader) populateGoals(ctx context.Context, goal *models.Goal) (*models.
} }
// Apply logs // Apply logs
result.Logs = make([]*models.LogResult, 0, len(logs))
result.Logs = make([]*models.GoalResultLog, 0, len(logs))
for _, log := range logs { for _, log := range logs {
resultLog := &models.LogResult{
Log: *log,
resultLog := &models.GoalResultLog{
LogResult: models.LogResult{Log: *log},
} }
contributes := false
for _, task := range tasks { for _, task := range tasks {
if task.ID == log.TaskID { if task.ID == log.TaskID {
@ -550,7 +551,7 @@ func (l *Loader) populateGoals(ctx context.Context, goal *models.Goal) (*models.
for _, item := range result.Items { for _, item := range result.Items {
amount := log.Amount(item.ID) amount := log.Amount(item.ID)
if amount > 0 && (goal.ItemID == nil || *goal.ItemID == item.ID) {
if amount > 0 && goal.Accepts(&item.Item, resultLog.Task) {
item.CompletedAmount += amount item.CompletedAmount += amount
if goal.Unweighted { if goal.Unweighted {
@ -558,21 +559,36 @@ func (l *Loader) populateGoals(ctx context.Context, goal *models.Goal) (*models.
} else { } else {
result.CompletedAmount += amount * item.GroupWeight result.CompletedAmount += amount * item.GroupWeight
} }
contributes = true
} else {
amount = 0
} }
if item.ID == log.ItemID { if item.ID == log.ItemID {
resultLog.Item = &item.Item resultLog.Item = &item.Item
if amount > 0 {
resultLog.ItemCounted = true
}
if log.SecondaryItemID == nil { if log.SecondaryItemID == nil {
break break
} }
} }
if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID { if log.SecondaryItemID != nil && item.ID == *log.SecondaryItemID {
if amount > 0 {
resultLog.SecondaryItemCounted = true
}
resultLog.SecondaryItem = &item.Item resultLog.SecondaryItem = &item.Item
} }
} }
result.Logs = append(result.Logs, resultLog)
if contributes {
result.Logs = append(result.Logs, resultLog)
}
} }
} }

19
svelte-ui/src/components/Composition.svelte

@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { GoalCompositionMode } from "../models/goal";
import type { GoalCompositionMode, GoalLogResult } from "../models/goal";
import type { LogResult } from "../models/log"; import type { LogResult } from "../models/log";
import type { TaskResult } from "../models/task";
interface CompositionItem { interface CompositionItem {
link: string link: string
@ -19,14 +18,16 @@ import type { TaskResult } from "../models/task";
if (mode === "item") { if (mode === "item") {
for (const log of logs) { for (const log of logs) {
const item = log.item;
if (!map[item.id]) {
map[item.id] = {name: item.name, amount: log.itemAmount, link: `/items#${item.id}`};
} else {
map[item.id].amount += log.itemAmount;
if (log.itemCounted !== false) {
const item = log.item;
if (!map[item.id]) {
map[item.id] = {name: item.name, amount: log.itemAmount, link: `/items#${item.id}`};
} else {
map[item.id].amount += log.itemAmount;
}
} }
if (log.secondaryItem) {
if (log.secondaryItem && log.secondaryItemCounted !== false) {
const item = log.secondaryItem; const item = log.secondaryItem;
if (!map[item.id]) { if (!map[item.id]) {
map[item.id] = {name: item.name, amount: log.secondaryItemAmount, link: `/items#${item.id}`}; map[item.id] = {name: item.name, amount: log.secondaryItemAmount, link: `/items#${item.id}`};
@ -49,7 +50,7 @@ import type { TaskResult } from "../models/task";
} }
} }
list = Object.keys(map).sort((a, b) => map[b].amount - map[a].amount).map(k => map[k]);
list = Object.keys(map).sort((a, b) => map[b].amount - map[a].amount).map(k => map[k]).filter(e => e.amount > 0);
} }
</script> </script>

14
svelte-ui/src/forms/GoalForm.svelte

@ -49,7 +49,9 @@ import type { GroupResult } from "../models/group";
let compositionMode = goal.compositionMode; let compositionMode = goal.compositionMode;
let startTime = formatFormTime(goal.startTime); let startTime = formatFormTime(goal.startTime);
let endTime = formatFormTime(goal.endTime); let endTime = formatFormTime(goal.endTime);
let taskFilter = goal.taskFilter || "";
let itemFilter = goal.itemFilter || "";
let error = null; let error = null;
let loading = false; let loading = false;
let selectedGroup: GroupResult = null; let selectedGroup: GroupResult = null;
@ -62,6 +64,8 @@ import type { GroupResult } from "../models/group";
startTime: new Date(startTime), startTime: new Date(startTime),
endTime: new Date(endTime), endTime: new Date(endTime),
itemId: itemId || null, itemId: itemId || null,
taskFilter: taskFilter.toLowerCase() || null,
itemFilter: itemFilter.toLowerCase() || null,
groupId, name, description, amount, unweighted, compositionMode groupId, name, description, amount, unweighted, compositionMode
}).then(() => { }).then(() => {
markStale("goal"); markStale("goal");
@ -86,6 +90,10 @@ import type { GroupResult } from "../models/group";
endTime: new Date(endTime), endTime: new Date(endTime),
itemId: itemId || null, itemId: itemId || null,
clearItemId: itemId === "", clearItemId: itemId === "",
taskFilter: taskFilter.toLowerCase() || null,
clearTaskFilter: taskFilter === "",
itemFilter: itemFilter.toLowerCase() || null,
clearItemFilter: itemFilter === "",
name, description, amount, compositionMode, unweighted, name, description, amount, compositionMode, unweighted,
}).then(() => { }).then(() => {
markStale("goal"); markStale("goal");
@ -128,6 +136,10 @@ import type { GroupResult } from "../models/group";
<input disabled={deletion} name="startTime" type="datetime-local" bind:value={startTime} /> <input disabled={deletion} name="startTime" type="datetime-local" bind:value={startTime} />
<label for="endTime">End Time</label> <label for="endTime">End Time</label>
<input disabled={deletion} name="endTime" type="datetime-local" bind:value={endTime} /> <input disabled={deletion} name="endTime" type="datetime-local" bind:value={endTime} />
<label for="taskFilter">Task Filter (Optional)</label>
<input disabled={deletion} name="taskFilter" type="text" bind:value={taskFilter} />
<label for="itemFilter">Item Filter (Optional)</label>
<input disabled={deletion} name="itemFilter" type="text" bind:value={itemFilter} />
<Checkbox bind:checked={unweighted} label="Unweighted (All items count as 1)" /> <Checkbox bind:checked={unweighted} label="Unweighted (All items count as 1)" />
<hr /> <hr />

15
svelte-ui/src/models/goal.ts

@ -15,6 +15,8 @@ export default interface Goal {
description: string description: string
unweighted: boolean unweighted: boolean
compositionMode: GoalCompositionMode compositionMode: GoalCompositionMode
taskFilter?: string
itemFilter?: string
} }
export interface GoalFilter { export interface GoalFilter {
@ -26,10 +28,15 @@ export interface GoalFilter {
export interface GoalResult extends Goal { export interface GoalResult extends Goal {
group: Group group: Group
items: GoalResultItem[] items: GoalResultItem[]
logs: LogResult[]
logs: GoalLogResult[]
completedAmount: number completedAmount: number
} }
export interface GoalLogResult extends LogResult {
itemCounted: boolean
secondaryItemCounted: boolean
}
interface GoalResultItem extends Item { interface GoalResultItem extends Item {
completedAmount: number completedAmount: number
} }
@ -44,6 +51,8 @@ export interface GoalInput {
description: string description: string
unweighted: boolean unweighted: boolean
compositionMode: string compositionMode: string
taskFilter?: string
itemFilter?: string
} }
export interface GoalUpdate { export interface GoalUpdate {
@ -56,4 +65,8 @@ export interface GoalUpdate {
unweighted?: boolean unweighted?: boolean
compositionMode?: string compositionMode?: string
clearItemId?: boolean clearItemId?: boolean
taskFilter?: string
clearTaskFilter: boolean
itemFilter?: string
clearItemFilter: boolean
} }

2
svelte-ui/src/models/log.ts

@ -21,6 +21,8 @@ export interface LogResult extends Log {
task: Task task: Task
item: Item item: Item
secondaryItem?: Item secondaryItem?: Item
itemCounted?: boolean
secondaryItemCounted?: boolean
} }
export interface LogInput { export interface LogInput {

Loading…
Cancel
Save