Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d2e027816 | ||
|
|
cac4ac0bf6 | ||
|
|
f5dee09e40 | ||
|
|
53be464d7e | ||
|
|
b381e2cbed | ||
|
|
d7c1fbb21c | ||
|
|
d4141f5eb3 | ||
|
|
1232721921 | ||
|
|
a70bb4bb23 | ||
|
|
0ba65038e9 | ||
|
|
6c3fdad79c | ||
|
|
e307ed6021 | ||
|
|
e4c41e1d47 | ||
|
|
ae47f1a0e5 | ||
|
|
b9ffe256e9 | ||
|
|
eff677cc69 | ||
|
|
702b2254c8 | ||
|
|
972ad748f2 | ||
|
|
2f4c03b9af | ||
|
|
0bbfe3689e | ||
|
|
a93ff5b292 | ||
|
|
ae408390a5 | ||
|
|
5987b7c8e1 | ||
|
|
e268c1fd97 | ||
|
|
7b7a46eae9 | ||
|
|
6825584b8d | ||
|
|
a530bed697 | ||
|
|
e65349662a | ||
|
|
fd5d732851 | ||
|
|
9259609efe | ||
|
|
a5b9f85eda | ||
|
|
c503b7a30e | ||
|
|
b089933188 | ||
|
|
2bd1e0a9e3 | ||
|
|
43b75f77c6 | ||
|
|
c8ca2d4344 | ||
|
|
6641d99d41 | ||
|
|
396b87eea1 | ||
|
|
1d59182d43 | ||
|
|
951be980c0 | ||
|
|
3a8bb9709f | ||
|
|
015c0c18b9 | ||
|
|
ed828b0227 | ||
|
|
d4ab0a8555 | ||
|
|
22885734c7 | ||
|
|
795d49a942 | ||
|
|
d083c9b1e8 | ||
|
|
0e9b1a4930 | ||
|
|
6bbbf33c83 | ||
|
|
ad53ec5c7e | ||
|
|
1195f6ab03 | ||
|
|
2bd68930fe | ||
|
|
0ef4e38b8a | ||
|
|
48b5c17166 | ||
|
|
2fcb6a5d1a | ||
|
|
0dfda26c09 | ||
|
|
701932546d | ||
|
|
287b18e2e1 | ||
|
|
75dd79127e | ||
|
|
da6cdfdb0d |
@@ -64,6 +64,7 @@ func main() {
|
|||||||
Stats: statsService,
|
Stats: statsService,
|
||||||
Repository: db.Items(),
|
Repository: db.Items(),
|
||||||
RequirementFetcher: db.Projects(),
|
RequirementFetcher: db.Projects(),
|
||||||
|
ProjectFetcher: db.Projects(),
|
||||||
}
|
}
|
||||||
projectsService := &projects.Service{
|
projectsService := &projects.Service{
|
||||||
Auth: authService,
|
Auth: authService,
|
||||||
@@ -96,6 +97,10 @@ func main() {
|
|||||||
httpapi.Items(apiV1ScopesSub.Group("/items"), itemsService)
|
httpapi.Items(apiV1ScopesSub.Group("/items"), itemsService)
|
||||||
httpapi.Sprints(apiV1ScopesSub.Group("/sprints"), sprintsService)
|
httpapi.Sprints(apiV1ScopesSub.Group("/sprints"), sprintsService)
|
||||||
httpapi.Stats(apiV1ScopesSub.Group("/stats"), statsService)
|
httpapi.Stats(apiV1ScopesSub.Group("/stats"), statsService)
|
||||||
|
apiV1AllScopes := apiV1.Group("/all-scopes")
|
||||||
|
apiV1AllScopes.Use(httpapi.AllScopeMiddleware(scopesService, authService))
|
||||||
|
httpapi.ItemsAllScopes(apiV1AllScopes, itemsService)
|
||||||
|
httpapi.SprintsAllScopes(apiV1AllScopes.Group("/sprints"), sprintsService)
|
||||||
|
|
||||||
ginLambda := ginadapter.New(server)
|
ginLambda := ginadapter.New(server)
|
||||||
lambda.Start(func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
|
lambda.Start(func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ func main() {
|
|||||||
Stats: statsService,
|
Stats: statsService,
|
||||||
Repository: db.Items(),
|
Repository: db.Items(),
|
||||||
RequirementFetcher: db.Projects(),
|
RequirementFetcher: db.Projects(),
|
||||||
|
ProjectFetcher: db.Projects(),
|
||||||
}
|
}
|
||||||
projectsService := &projects.Service{
|
projectsService := &projects.Service{
|
||||||
Auth: authService,
|
Auth: authService,
|
||||||
@@ -94,6 +95,10 @@ func main() {
|
|||||||
httpapi.Items(apiV1ScopesSub.Group("/items"), itemsService)
|
httpapi.Items(apiV1ScopesSub.Group("/items"), itemsService)
|
||||||
httpapi.Sprints(apiV1ScopesSub.Group("/sprints"), sprintsService)
|
httpapi.Sprints(apiV1ScopesSub.Group("/sprints"), sprintsService)
|
||||||
httpapi.Stats(apiV1ScopesSub.Group("/stats"), statsService)
|
httpapi.Stats(apiV1ScopesSub.Group("/stats"), statsService)
|
||||||
|
apiV1AllScopes := apiV1.Group("/all-scopes")
|
||||||
|
apiV1AllScopes.Use(httpapi.AllScopeMiddleware(scopesService, authService))
|
||||||
|
httpapi.ItemsAllScopes(apiV1AllScopes.Group("/items"), itemsService)
|
||||||
|
httpapi.SprintsAllScopes(apiV1AllScopes.Group("/sprints"), sprintsService)
|
||||||
|
|
||||||
exitSignal := make(chan os.Signal)
|
exitSignal := make(chan os.Signal)
|
||||||
signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM)
|
signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM)
|
||||||
|
|||||||
@@ -25,6 +25,14 @@ type ItemStat struct {
|
|||||||
Required int `json:"required"`
|
Required int `json:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (item *Item) AcquiredOutside(fromTime, toTime time.Time) bool {
|
||||||
|
return item.AcquiredTime != nil && (item.AcquiredTime.Before(fromTime) || !item.AcquiredTime.Before(toTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (item *Item) AcquiredBetween(fromTime, toTime time.Time) bool {
|
||||||
|
return item.AcquiredTime != nil && !item.AcquiredTime.Before(fromTime) && item.AcquiredTime.Before(toTime)
|
||||||
|
}
|
||||||
|
|
||||||
func (item *Item) ApplyUpdate(update models.ItemUpdate) {
|
func (item *Item) ApplyUpdate(update models.ItemUpdate) {
|
||||||
if update.RequirementID != nil {
|
if update.RequirementID != nil {
|
||||||
if *update.RequirementID <= 0 {
|
if *update.RequirementID <= 0 {
|
||||||
|
|||||||
+11
-7
@@ -31,13 +31,14 @@ func (project *Project) Update(update models.ProjectUpdate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Requirement struct {
|
type Requirement struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
ScopeID int `json:"scopeId"`
|
ScopeID int `json:"scopeId"`
|
||||||
ProjectID int `json:"projectId"`
|
ProjectID int `json:"projectId"`
|
||||||
Name string `json:"name"`
|
AggregateRequired int `json:"aggregateRequired"`
|
||||||
Description string `json:"description"`
|
Name string `json:"name"`
|
||||||
IsCoarse bool `json:"isCoarse"`
|
Description string `json:"description"`
|
||||||
Status models.Status `json:"status"`
|
IsCoarse bool `json:"isCoarse"`
|
||||||
|
Status models.Status `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (requirement *Requirement) Update(update models.RequirementUpdate) {
|
func (requirement *Requirement) Update(update models.RequirementUpdate) {
|
||||||
@@ -53,6 +54,9 @@ func (requirement *Requirement) Update(update models.RequirementUpdate) {
|
|||||||
if update.IsCoarse != nil {
|
if update.IsCoarse != nil {
|
||||||
requirement.IsCoarse = *update.IsCoarse
|
requirement.IsCoarse = *update.IsCoarse
|
||||||
}
|
}
|
||||||
|
if update.AggregateRequired != nil {
|
||||||
|
requirement.AggregateRequired = *update.AggregateRequired
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequirementStat struct {
|
type RequirementStat struct {
|
||||||
|
|||||||
Vendored
+1
@@ -13,6 +13,7 @@ declare namespace App {
|
|||||||
user?: import("$lib/models/user").default
|
user?: import("$lib/models/user").default
|
||||||
idToken?: string
|
idToken?: string
|
||||||
req?: any
|
req?: any
|
||||||
|
groupOptions?: import("$lib/models/items").GroupItemsOptions
|
||||||
amplifyConfig: any
|
amplifyConfig: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ export const handle: Handle = async({ event, resolve }) => {
|
|||||||
for (const key in newCookies) {
|
for (const key in newCookies) {
|
||||||
response.headers.append("Set-Cookie", cookie.serialize(key, newCookies[key], {
|
response.headers.append("Set-Cookie", cookie.serialize(key, newCookies[key], {
|
||||||
path: "/",
|
path: "/",
|
||||||
|
maxAge: 8640000,
|
||||||
|
expires: new Date(Date.now() + 8640000000),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { TimeInterval } from "$lib/models/common";
|
import type { TimeInterval } from "$lib/models/common";
|
||||||
import type Item from "$lib/models/item";
|
import type Item from "$lib/models/item";
|
||||||
import type { ItemFilter, ItemInput } from "$lib/models/item";
|
import type { GroupItemsOptions, ItemFilter, ItemGroup, ItemInput } from "$lib/models/item";
|
||||||
import type Project from "$lib/models/project";
|
import type Project from "$lib/models/project";
|
||||||
import type { ProjectEntry, ProjectInput, Requirement, RequirementInput } from "$lib/models/project";
|
import type { ProjectEntry, ProjectInput, Requirement, RequirementInput } from "$lib/models/project";
|
||||||
import type { ScopeInput } from "$lib/models/scope";
|
import type { ScopeInput } from "$lib/models/scope";
|
||||||
@@ -73,8 +73,19 @@ export default class SL3APIClient {
|
|||||||
return this.fetch<{project: Project}>("PUT", `scopes/${scopeId}/projects/${projectId}`, input).then(r => r.project);
|
return this.fetch<{project: Project}>("PUT", `scopes/${scopeId}/projects/${projectId}`, input).then(r => r.project);
|
||||||
}
|
}
|
||||||
|
|
||||||
async listItems(scopeId: number, filter: ItemFilter): Promise<Item[]> {
|
async listItems(scopeId: number | "ALL", filter: ItemFilter, grouped: true, groupOptions: GroupItemsOptions): Promise<{items: Item[], groups: ItemGroup[]}>
|
||||||
return this.fetch<{items: Item[]}>("GET", `scopes/${scopeId}/items?filter=${encodeURIComponent(JSON.stringify(filter))}`).then(r => r.items);
|
async listItems(scopeId: number | "ALL", filter: ItemFilter, grouped?: false): Promise<Item[]>
|
||||||
|
async listItems(scopeId: number | "ALL", filter: ItemFilter, grouped?: boolean, groupOptions?: GroupItemsOptions): Promise<Item[] | {items: Item[], groups: ItemGroup[]}> {
|
||||||
|
const scopePrefix = (scopeId !== "ALL") ? `scopes/${scopeId}` : "all-scopes";
|
||||||
|
const groupedSuffix = !!grouped ? `&grouped=true&hideCreated=${groupOptions?.hideCreated}&hideAcquired=${groupOptions?.hideAcquired}&hideScheduled=${groupOptions?.hideScheduled}` : "";
|
||||||
|
|
||||||
|
return this.fetch<{items: Item[], groups: ItemGroup[]}>("GET", `${scopePrefix}/items?filter=${encodeURIComponent(JSON.stringify(filter))}${groupedSuffix}`).then(r => {
|
||||||
|
if (!grouped) {
|
||||||
|
return r.items
|
||||||
|
}
|
||||||
|
|
||||||
|
return {groups: r.groups, items: r.items}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createItem(scopeId: number, input: ItemInput): Promise<Item> {
|
async createItem(scopeId: number, input: ItemInput): Promise<Item> {
|
||||||
@@ -97,15 +108,17 @@ export default class SL3APIClient {
|
|||||||
return this.fetch<{sprint: Sprint}>("GET", `scopes/${scopeId}/sprints/${sprintId}`).then(r => r.sprint);
|
return this.fetch<{sprint: Sprint}>("GET", `scopes/${scopeId}/sprints/${sprintId}`).then(r => r.sprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
async listSprints(scopeId: number, interval?: TimeInterval<Date | string>): Promise<Sprint[]> {
|
async listSprints(scopeId: number | "ALL", interval?: TimeInterval<Date | string>): Promise<Sprint[]> {
|
||||||
let qs = "";
|
let qs = "";
|
||||||
if (interval != null) {
|
if (interval != null) {
|
||||||
const minStr = (interval.min instanceof Date) ? interval.min.toISOString() : interval.min;
|
const minStr = (interval.min instanceof Date) ? interval.min.toISOString() : interval.min;
|
||||||
const maxStr = (interval.max instanceof Date) ? interval.max.toISOString() : interval.max;
|
const maxStr = (interval.max instanceof Date) ? interval.max.toISOString() : interval.max;
|
||||||
qs = `?from=${encodeURIComponent(minStr)}&to=${encodeURIComponent(maxStr)}`
|
qs = `?from=${encodeURIComponent(minStr)}&to=${encodeURIComponent(maxStr)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let prefix = (scopeId === "ALL") ? `all-scopes` : `scopes/${scopeId}`
|
||||||
|
|
||||||
return this.fetch<{sprints: Sprint[]}>("GET", `scopes/${scopeId}/sprints${qs}`).then(r => r.sprints);
|
return this.fetch<{sprints: Sprint[]}>("GET", `${prefix}/sprints${qs}`).then(r => r.sprints);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSprint(scopeId: number, input: SprintInput): Promise<Sprint> {
|
async createSprint(scopeId: number, input: SprintInput): Promise<Sprint> {
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import PlusMinus from "../layout/PlusMinus.svelte"
|
import shortestDecimal from "$lib/utils/numbers";
|
||||||
|
|
||||||
|
import PlusMinus from "../layout/PlusMinus.svelte"
|
||||||
|
|
||||||
export let label: string
|
export let label: string
|
||||||
export let value: number
|
export let value: number
|
||||||
export let target: number = null
|
export let target: number = null
|
||||||
|
export let right: boolean = false;
|
||||||
|
export let dark: boolean = false;
|
||||||
|
|
||||||
|
let valueRounded: number;
|
||||||
|
let targetRounded: number;
|
||||||
|
let valueString: string;
|
||||||
|
let targetString: string;
|
||||||
|
$: {
|
||||||
|
valueRounded = Math.round(value * 1000) / 1000;
|
||||||
|
|
||||||
|
if (target != null) {
|
||||||
|
targetRounded = Math.round(target * 1000) / 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="amount">
|
<div class="amount" class:right>
|
||||||
<span class="value">{value}×</span>
|
<span class:dark class="value">{shortestDecimal(valueRounded)}×</span>
|
||||||
<span class="label">{label}</span>
|
<span class="label">{label}</span>
|
||||||
{ #if target != null && target != value }
|
{ #if target != null && targetRounded != valueRounded }
|
||||||
<span class="target">(<PlusMinus offset={value - target} />)</span>
|
<span class="target">(<PlusMinus offset={valueRounded - targetRounded} />)</span>
|
||||||
{ /if }
|
{ /if }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -19,9 +35,14 @@ import PlusMinus from "../layout/PlusMinus.svelte"
|
|||||||
|
|
||||||
div.amount {
|
div.amount {
|
||||||
padding-right: 1ch;
|
padding-right: 1ch;
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
padding-right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
span.label {
|
span.label, span.dark {
|
||||||
opacity: $opacity-entry4;
|
opacity: $opacity-entry4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,24 +8,26 @@
|
|||||||
<div class="par">(</div>
|
<div class="par">(</div>
|
||||||
<div class="selector">
|
<div class="selector">
|
||||||
<select value={value} on:change>
|
<select value={value} on:change>
|
||||||
<option value="last:7d">Last 7 days</option>
|
<option selected={value === "last:7d" } value="last:7d">Last 7 days</option>
|
||||||
<option value="last:14d">Last 14 days</option>
|
<option selected={value === "last:14d" } value="last:14d">Last 14 days</option>
|
||||||
<option value="last:30d">Last 30 days</option>
|
<option selected={value === "last:30d" } value="last:30d">Last 30 days</option>
|
||||||
<option value="last:90d">Last 90 days</option>
|
<option selected={value === "last:90d" } value="last:90d">Last 90 days</option>
|
||||||
<option value="last:1m">Last 1 month</option>
|
<option selected={value === "last:1m" } value="last:1m">Last 1 month</option>
|
||||||
<option value="last:3m">Last 3 months</option>
|
<option selected={value === "last:3m" } value="last:3m">Last 3 months</option>
|
||||||
<option value="last:6m">Last 6 months</option>
|
<option selected={value === "last:6m" } value="last:6m">Last 6 months</option>
|
||||||
<option value="last:1y">Last 1 year</option>
|
<option selected={value === "last:1y" } value="last:1y">Last 1 year</option>
|
||||||
<option value="this_week">This week</option>
|
<option selected={value === "this_week" } value="this_week">This week</option>
|
||||||
<option value="next_week">Next week</option>
|
<option selected={value === "next_week" } value="next_week">Next week</option>
|
||||||
<option value="last_week">Last week</option>
|
<option selected={value === "last_week" } value="last_week">Last week</option>
|
||||||
<option value="this_month">This month</option>
|
<option selected={value === "this_month" } value="this_month">This month</option>
|
||||||
<option value="next_month">Next month</option>
|
<option selected={value === "next_month" } value="next_month">Next month</option>
|
||||||
<option value="last_month">Last month</option>
|
<option selected={value === "last_month" } value="last_month">Last month</option>
|
||||||
<option value="all_time">All time</option>
|
<option selected={value === "all_time" } value="all_time">All time</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="par">)</div>
|
<div class="par">)
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { significantDecimals } from "$lib/utils/numbers";
|
||||||
|
|
||||||
export let target = 1;
|
export let target = 1;
|
||||||
export let count = 0;
|
export let count = 0;
|
||||||
export let green = false;
|
export let green = false;
|
||||||
@@ -38,7 +40,7 @@
|
|||||||
{#if percentageOnly}
|
{#if percentageOnly}
|
||||||
<span class="ackreq">({((count / target) * 100).toFixed(0)}%)</span>
|
<span class="ackreq">({((count / target) * 100).toFixed(0)}%)</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="ackreq">({Math.floor(count)} / {Math.ceil(target)})</span>
|
<span class="ackreq">({significantDecimals(count)} / {significantDecimals(target)})</span>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,10 +10,19 @@
|
|||||||
|
|
||||||
<div class="markdown">{@html outputHtml}</div>
|
<div class="markdown">{@html outputHtml}</div>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
|
@import "../../css/colors.sass";
|
||||||
|
|
||||||
div.markdown :global(p) {
|
div.markdown :global(p) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0.25em 0;
|
margin: 0.5em 0;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.markdown :global(code) {
|
div.markdown :global(code) {
|
||||||
@@ -58,6 +67,17 @@
|
|||||||
counter-reset: li;
|
counter-reset: li;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.markdown :global(a) {
|
||||||
|
color: #d1a31b;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: 250ms;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #FFCC11;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
div.markdown :global(ul) :global(li:before) {
|
div.markdown :global(ul) :global(li:before) {
|
||||||
content: "\2022"; /* Add content: \2022 is the CSS Code/unicode for a bullet */
|
content: "\2022"; /* Add content: \2022 is the CSS Code/unicode for a bullet */
|
||||||
color: #333; /* Change the color */
|
color: #333; /* Change the color */
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 749px) {
|
||||||
> div {
|
> div {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,10 @@
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
|
@media screen and (max-width: 749px) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.title {
|
div.title {
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
padding-left: 0
|
padding-left: 0
|
||||||
flex: 1
|
flex: 1
|
||||||
|
|
||||||
div.modal-body:first-of-type
|
@media screen and (max-width: 749px)
|
||||||
padding-left: 1em
|
padding-left: 1em
|
||||||
|
padding: 0 1em
|
||||||
|
|
||||||
|
&:first-of-type
|
||||||
|
padding-left: 1em
|
||||||
</style>
|
</style>
|
||||||
@@ -16,6 +16,6 @@
|
|||||||
3: "spinner",
|
3: "spinner",
|
||||||
4: "check",
|
4: "check",
|
||||||
5: "times",
|
5: "times",
|
||||||
6: "times",
|
6: "trash",
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -25,7 +25,6 @@
|
|||||||
import { getScopeContext } from "./ScopeContext.svelte";
|
import { getScopeContext } from "./ScopeContext.svelte";
|
||||||
import type Item from "$lib/models/item";
|
import type Item from "$lib/models/item";
|
||||||
import type { ItemFilter, ItemGroup } from "$lib/models/item";
|
import type { ItemFilter, ItemGroup } from "$lib/models/item";
|
||||||
import { groupItems } from "$lib/utils/items";
|
|
||||||
|
|
||||||
export let items: Item[];
|
export let items: Item[];
|
||||||
export let groups: ItemGroup[];
|
export let groups: ItemGroup[];
|
||||||
@@ -50,9 +49,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newItems = await sl3(fetch).listItems($scope.id, filter)
|
const res = await sl3(fetch).listItems($scope.id, filter, true, {});
|
||||||
itemsWritable.set(newItems);
|
itemsWritable.set(res.items);
|
||||||
groupsWritable.set(groupItems(newItems, filter.scheduledDate.min))
|
groupsWritable.set(res.groups);
|
||||||
} catch(_) {}
|
} catch(_) {}
|
||||||
|
|
||||||
loading = false;
|
loading = false;
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
const contextKey = {ctx: "itemMultiListContext"};
|
||||||
|
|
||||||
|
interface ItemMultiListContextData {
|
||||||
|
lists: Readable<Record<string, Item[]>>,
|
||||||
|
reloadItemLists(): void,
|
||||||
|
};
|
||||||
|
|
||||||
|
const fallback: ItemMultiListContextData = {
|
||||||
|
lists: readable({}),
|
||||||
|
reloadItemLists() {},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getItemMultiListContext() {
|
||||||
|
return getContext(contextKey) as ItemMultiListContextData || fallback
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { readable, writable, type Readable } from "svelte/store";
|
||||||
|
import { getContext, setContext } from "svelte";
|
||||||
|
import type Item from "$lib/models/item";
|
||||||
|
import type { ItemFilter } from "$lib/models/item";
|
||||||
|
import { getScopeContext } from "./ScopeContext.svelte";
|
||||||
|
import deepEqual from "$lib/utils/object";
|
||||||
|
import { sl3 } from "$lib/clients/sl3";
|
||||||
|
|
||||||
|
const {scope} = getScopeContext();
|
||||||
|
|
||||||
|
export let lists: Record<string, Item[]>
|
||||||
|
export let filters: Record<string, ItemFilter>
|
||||||
|
export let global: boolean = false;
|
||||||
|
|
||||||
|
let loaded: Record<string, ItemFilter> = {...filters};
|
||||||
|
let loading: Record<string, boolean> = {};
|
||||||
|
const listsWritable = writable<Record<string, Item[]>>({...lists});
|
||||||
|
|
||||||
|
function reloadItemLists() {
|
||||||
|
loaded = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runLoad(key: string, filter: ItemFilter) {
|
||||||
|
loading = {...loading, [key]: true};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const items = await sl3(fetch).listItems(global ? "ALL" : $scope.id, filter)
|
||||||
|
listsWritable.update(l => ({...l, [key.replace("Filter", "Items")]: items}));
|
||||||
|
} catch(_err) {
|
||||||
|
// TODO: Use err
|
||||||
|
}
|
||||||
|
|
||||||
|
loading = {...loading, [key]: false};
|
||||||
|
loaded = {...loaded, [key]: filter};
|
||||||
|
}
|
||||||
|
|
||||||
|
setContext<ItemMultiListContextData>(contextKey, {
|
||||||
|
lists: {subscribe: listsWritable.subscribe},
|
||||||
|
reloadItemLists,
|
||||||
|
});
|
||||||
|
|
||||||
|
$: for (const key in filters) {
|
||||||
|
if (loading[key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loaded[key] == null || !deepEqual(filters[key], loaded[key])) {
|
||||||
|
runLoad(key, {...filters[key]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot></slot>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fallback: ProjectContextData = {
|
const fallback: ProjectContextData = {
|
||||||
project: writable({} as Project),
|
project: readable({requirements: []} as Project),
|
||||||
reloadProject: () => Promise.resolve()
|
reloadProject: () => Promise.resolve()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type Project from "$lib/models/project";
|
import type Project from "$lib/models/project";
|
||||||
import { writable, type Readable } from "svelte/store";
|
import { readable, writable, type Readable } from "svelte/store";
|
||||||
import { getContext, setContext } from "svelte";
|
import { getContext, setContext } from "svelte";
|
||||||
import { sl3 } from "$lib/clients/sl3";
|
import { sl3 } from "$lib/clients/sl3";
|
||||||
import { getScopeContext } from "./ScopeContext.svelte";
|
import { getScopeContext } from "./ScopeContext.svelte";
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fallback: ScopeContextData = {
|
const fallback: ScopeContextData = {
|
||||||
scope: readable({} as Scope),
|
scope: readable({stats: []} as Scope),
|
||||||
lastHistoryRange: writable(""),
|
lastHistoryRange: writable("last:14d"),
|
||||||
upsertStat: () => {},
|
upsertStat: () => {},
|
||||||
deleteStat: () => {},
|
deleteStat: () => {},
|
||||||
updateScope: () => {},
|
updateScope: () => {},
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
export let sprints: Sprint[];
|
export let sprints: Sprint[];
|
||||||
export let intervalString: string;
|
export let intervalString: string;
|
||||||
|
export let global: boolean = false;
|
||||||
|
|
||||||
const {scope} = getScopeContext();
|
const {scope} = getScopeContext();
|
||||||
const {now} = getTimeContext();
|
const {now} = getTimeContext();
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newSprints = await sl3(fetch).listSprints($scope.id, parseInterval(intervalString, $now));
|
const newSprints = await sl3(fetch).listSprints(global ? "ALL" : $scope.id, parseInterval(intervalString, $now));
|
||||||
sprintsWritable.set(newSprints);
|
sprintsWritable.set(newSprints);
|
||||||
} catch(_) {}
|
} catch(_) {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,69 +1,146 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
|
export interface PartOptionGroup {
|
||||||
|
id: number
|
||||||
|
label: string
|
||||||
|
subs: PartOptionGroup[]
|
||||||
|
options: PartOption[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface PartOption {
|
export interface PartOption {
|
||||||
partId: number
|
partId: number
|
||||||
name: string
|
name: string
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
required: number
|
required: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SO = [
|
||||||
|
Status.Active, Status.Background, Status.Available, Status.Blocked,
|
||||||
|
Status.Completed, Status.Failed, Status.Dropped,
|
||||||
|
]
|
||||||
|
|
||||||
|
function compareStatus<T extends {status: Status, id: number}>(a: T, b: T): number {
|
||||||
|
if (a.status === b.status) {
|
||||||
|
return a.id - b.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SO.indexOf(a.status) - SO.indexOf(b.status)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { browser } from "$app/env";
|
import { browser } from "$app/env";
|
||||||
import { sl3 } from "$lib/clients/sl3";
|
import { sl3 } from "$lib/clients/sl3";
|
||||||
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
||||||
|
import type Scope from "$lib/models/scope";
|
||||||
import { SprintKind, type SprintInputPart } from "$lib/models/sprint";
|
import { SprintKind, type SprintInputPart } from "$lib/models/sprint";
|
||||||
|
import Status from "$lib/models/status";
|
||||||
|
import shortestDecimal from "$lib/utils/numbers";
|
||||||
|
import { getScopeListContext } from "../contexts/ScopeListContext.svelte";
|
||||||
import Checkbox from "../layout/Checkbox.svelte";
|
import Checkbox from "../layout/Checkbox.svelte";
|
||||||
|
|
||||||
export let value: SprintInputPart[] = [];
|
export let value: SprintInputPart[] = [];
|
||||||
export let kind: SprintKind;
|
export let kind: SprintKind;
|
||||||
|
export let scopeId: number;
|
||||||
|
|
||||||
const {scope} = getScopeContext();
|
const {scope} = getScopeContext();
|
||||||
|
const {scopes} = getScopeListContext();
|
||||||
|
|
||||||
let error = "";
|
let error = "";
|
||||||
let options: PartOption[] = [];
|
let groups: PartOptionGroup[] = [];
|
||||||
let showRequired = false;
|
let showRequired = false;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
let onlyActive = true;
|
||||||
let loaded: SprintKind = SprintKind.Invalid
|
let loaded: SprintKind = SprintKind.Invalid
|
||||||
|
let hiddenSelections: SprintInputPart[] = [];
|
||||||
|
let actualScope: Scope;
|
||||||
|
|
||||||
async function load(kind: SprintKind) {
|
function forceReload() {
|
||||||
|
loaded = SprintKind.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load(kind: SprintKind, scope: Scope) {
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case SprintKind.Items: {
|
case SprintKind.Items: {
|
||||||
const items = await sl3(fetch).listItems($scope.id, {});
|
const items = await sl3(fetch).listItems(scope.id, {unAcquired: onlyActive});
|
||||||
options = items.map(i => ({
|
const projects = items.filter(i => i.project != null).map(i => i.project).sort(compareStatus).filter((p, i, a) => p.id != a[i-1]?.id);
|
||||||
enabled: false,
|
const requirements = items.filter(i => i.requirement != null).map(i => i.requirement).sort(compareStatus).filter((r, i, a) => r.id != a[i-1]?.id);
|
||||||
name: `${i.name}${i.acquiredTime?" ✓":""}`,
|
const loose = items.filter(i => i.requirementId == null);
|
||||||
partId: i.id,
|
const reqProjectIdMap = items.filter(i => i.projectId != null && i.requirementId != null).reduce((p, c) => ({
|
||||||
required: 0,
|
...p,
|
||||||
|
[c.requirementId]: c.projectId,
|
||||||
|
}), {} as Record<number, string>)
|
||||||
|
|
||||||
|
groups = projects.filter(p => !onlyActive || p.status === Status.Active || p.status === Status.Background).map(p => ({
|
||||||
|
id: p.id,
|
||||||
|
label: `${p.name} (${p.statusName})`,
|
||||||
|
options: [],
|
||||||
|
subs: requirements.filter(r => !onlyActive || r.status === Status.Active || r.status === Status.Background).filter(r => reqProjectIdMap[r.id] === p.id).map(r => ({
|
||||||
|
id: r.id,
|
||||||
|
label: `${r.name} (${r.statusName})`,
|
||||||
|
options: items.filter(i => i.requirementId === r.id).map(i => ({
|
||||||
|
enabled: false,
|
||||||
|
partId: i.id,
|
||||||
|
name: `${i.name} (${shortestDecimal(i.weightedRequired)})${!!i.acquiredTime?" ✓":""}`,
|
||||||
|
required: 0,
|
||||||
|
})).sort((a,b) => a.partId - b.partId),
|
||||||
|
subs: [],
|
||||||
|
}))
|
||||||
}))
|
}))
|
||||||
|
groups.push({id: -1, label: "Loose items", subs: [], options: loose.map(i => ({
|
||||||
|
enabled: false,
|
||||||
|
partId: i.id,
|
||||||
|
name: `${i.name} (${shortestDecimal(i.weightedRequired)})${!!i.acquiredTime?" ✓":""}`,
|
||||||
|
required: 0,
|
||||||
|
})).sort((a,b) => a.partId - b.partId)})
|
||||||
|
|
||||||
|
const allParts = groups.flatMap(g => [...g.subs.flatMap(g => g.options), ...g.options]);
|
||||||
|
hiddenSelections = value.filter(v => !allParts.find(p => p.partId === v.partId));
|
||||||
|
|
||||||
showRequired = false;
|
showRequired = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SprintKind.Requirements: {
|
case SprintKind.Requirements: {
|
||||||
options = [];
|
groups = [];
|
||||||
throw "Requirement selection is not yet supported the UI."
|
throw "Requirement selection is not yet supported the UI."
|
||||||
}
|
}
|
||||||
case SprintKind.Stats: {
|
case SprintKind.Stats: {
|
||||||
options = $scope.stats.map(s => ({partId: s.id, name: s.name, enabled: false, required: 0}));
|
groups = [{
|
||||||
|
id: -1,
|
||||||
|
label: "",
|
||||||
|
subs: [],
|
||||||
|
options: scope.stats.map(s => ({partId: s.id, name: s.name, enabled: false, required: 0})),
|
||||||
|
}];
|
||||||
showRequired = true;
|
showRequired = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SprintKind.Scope: {
|
case SprintKind.Scope: {
|
||||||
options = [];
|
groups = [];
|
||||||
showRequired = false;
|
showRequired = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
error = "";
|
error = "";
|
||||||
for (const opt of options) {
|
for (const group of groups) {
|
||||||
const val = value.find(v => v.partId === opt.partId);
|
for (const opt of group.options) {
|
||||||
if (val != null) {
|
const val = value.find(v => v.partId === opt.partId);
|
||||||
opt.enabled = true;
|
if (val != null) {
|
||||||
opt.required = val.required;
|
opt.enabled = true;
|
||||||
|
opt.required = val.required;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subGroup of group.subs) {
|
||||||
|
for (const opt of subGroup.options) {
|
||||||
|
const val = value.find(v => v.partId === opt.partId);
|
||||||
|
if (val != null) {
|
||||||
|
opt.enabled = true;
|
||||||
|
opt.required = val.required;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
@@ -74,26 +151,55 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (loaded === kind && error === "") {
|
// Hax to make it reload if onlyActive changed.
|
||||||
value = options.filter(o => o.enabled).map(o => ({partId: o.partId, required: o.required}))
|
$: if (onlyActive != null) {
|
||||||
|
forceReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if (loaded === kind && error === "") {
|
||||||
|
value = [
|
||||||
|
...hiddenSelections,
|
||||||
|
...groups.flatMap(g => [...g.subs.flatMap(g => g.options), ...g.options]).filter(o => o.enabled).map(o => ({partId: o.partId, required: o.required})),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
$: actualScope = ($scope.id === scopeId || scopeId == null) ? $scope : $scopes.find(s => s.id === scopeId);
|
||||||
|
|
||||||
$: if (browser && !loading && loaded !== kind) {
|
$: if (browser && !loading && loaded !== kind) {
|
||||||
load(kind);
|
load(kind, actualScope);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="part-input">
|
<div class="part-input">
|
||||||
<div class="error">{error}</div>
|
<div class="error">{error}</div>
|
||||||
{#each options as option (option.partId)}
|
{#each groups as group (group.id)}
|
||||||
<div class="part">
|
<div class="label">{group.label}</div>
|
||||||
<Checkbox noLabel bind:checked={option.enabled} />
|
{#each group.subs as group (group.id)}
|
||||||
<div class="name" class:disabled={!option.enabled}>{option.name}</div>
|
<div class="label label-2">{group.label}</div>
|
||||||
{#if showRequired && option.enabled}
|
{#each group.options as option (option.partId)}
|
||||||
<input type="number" class="required" bind:value={option.required} />
|
<div class="part">
|
||||||
{/if}
|
<Checkbox noLabel bind:checked={option.enabled} />
|
||||||
</div>
|
<div class="name" class:disabled={!option.enabled}>{option.name}</div>
|
||||||
|
{#if showRequired && option.enabled}
|
||||||
|
<input type="number" class="required" bind:value={option.required} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/each}
|
||||||
|
{#each group.options as option (option.partId)}
|
||||||
|
<div class="part">
|
||||||
|
<Checkbox noLabel bind:checked={option.enabled} />
|
||||||
|
<div class="name" class:disabled={!option.enabled}>{option.name}</div>
|
||||||
|
{#if showRequired && option.enabled}
|
||||||
|
<input type="number" class="required" bind:value={option.required} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if kind === SprintKind.Items}
|
||||||
|
<hr />
|
||||||
|
<Checkbox label="Show only active items" bind:checked={onlyActive} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -103,7 +209,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-top: 1px solid $color-entry1;
|
|
||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
@@ -123,7 +228,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
padding: 0.3em 0.5ch;
|
padding: 0.25em 0.5ch;
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
color: $color-entry2;
|
color: $color-entry2;
|
||||||
@@ -131,11 +236,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin-top: 2em;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid $color-entry2;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.label {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
border-bottom: 1px solid $color-entry2;
|
||||||
|
|
||||||
|
&:empty {
|
||||||
|
margin-top: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.label-2 {
|
||||||
|
margin-top: 1em;
|
||||||
|
font-size: 0.75em;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&+div.label-2 {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
div.part-input {
|
div.part-input {
|
||||||
width: calc(100% - 2ch);
|
width: calc(100% - 2ch);
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
margin-top: 0.20em;
|
margin-top: 0.20em;
|
||||||
height: 12em;
|
height: 26em;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
background: $color-entryhalf;
|
background: $color-entryhalf;
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Requirement } from "$lib/models/project";
|
||||||
|
|
||||||
|
export let value: number;
|
||||||
|
export let requirements: Requirement[];
|
||||||
|
export let disabled: boolean = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<select disabled={disabled} bind:value={value}>
|
||||||
|
{#each requirements as req (req.id)}
|
||||||
|
<option value={req.id}>{req.name} ({req.statusName})</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
||||||
|
import { getScopeListContext } from "../contexts/ScopeListContext.svelte";
|
||||||
import type { StatValueInput } from "$lib/models/stat";
|
import type { StatValueInput } from "$lib/models/stat";
|
||||||
import Checkbox from "../layout/Checkbox.svelte";
|
import Checkbox from "../layout/Checkbox.svelte";
|
||||||
|
import type Scope from "$lib/models/scope";
|
||||||
|
|
||||||
export let value: StatValueInput[] = [];
|
export let value: StatValueInput[] = [];
|
||||||
export let showAcquired = false;
|
export let showAcquired = false;
|
||||||
@@ -9,50 +11,56 @@
|
|||||||
export let hideUnseen = false;
|
export let hideUnseen = false;
|
||||||
export let zeroDeletes = false;
|
export let zeroDeletes = false;
|
||||||
export let noToggle = false;
|
export let noToggle = false;
|
||||||
|
export let initOnList: number[] = [];
|
||||||
|
export let overrideScopeId: number = null;
|
||||||
|
|
||||||
|
const {scope} = getScopeContext();
|
||||||
|
const {scopes} = getScopeListContext();
|
||||||
|
|
||||||
let acquiredMap: Record<number, number>;
|
let acquiredMap: Record<number, number>;
|
||||||
let requiredMap: Record<number, number>;
|
let requiredMap: Record<number, number>;
|
||||||
let enabledMap: Record<number, boolean>;
|
let enabledMap: Record<number, boolean>;
|
||||||
|
let actualScope: Scope;
|
||||||
|
|
||||||
function initialize() {
|
function initialize(actualScope: Scope) {
|
||||||
acquiredMap = {};
|
acquiredMap = {};
|
||||||
requiredMap = {};
|
requiredMap = {};
|
||||||
enabledMap = {};
|
enabledMap = {};
|
||||||
|
|
||||||
if (hideUnseen) {
|
if (hideUnseen) {
|
||||||
for (const v of value) {
|
for (const v of value) {
|
||||||
enabledMap[v.statId] = zeroDeletes ? v.required > 0 : v.required >= 0;
|
enabledMap[v.statId] = initOnList.includes(v.statId) || (zeroDeletes ? v.required > 0 : v.required >= 0);
|
||||||
requiredMap[v.statId] = v.required;
|
requiredMap[v.statId] = v.required;
|
||||||
acquiredMap[v.statId] = v.acquired;
|
acquiredMap[v.statId] = v.acquired;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const stat of $scope.stats) {
|
for (const stat of actualScope.stats) {
|
||||||
const found = value.find(v => v.statId == stat.id);
|
const found = value.find(v => v.statId == stat.id);
|
||||||
|
|
||||||
enabledMap[stat.id] = found != null;
|
enabledMap[stat.id] = found != null && (found?.required > (zeroDeletes ? 0 : -1));
|
||||||
requiredMap[stat.id] = found?.required || 0;
|
requiredMap[stat.id] = found?.required || 0;
|
||||||
acquiredMap[stat.id] = found?.acquired || 0;
|
acquiredMap[stat.id] = found?.acquired || 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: actualScope = overrideScopeId !== null && $scopes.length > 0 ? $scopes.find(s => s.id === overrideScopeId) : $scope;
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (acquiredMap == null) {
|
if (acquiredMap == null) {
|
||||||
initialize();
|
initialize(actualScope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: value = $scope.stats.filter(s => enabledMap[s.id]).map(s => ({
|
$: value = actualScope.stats.filter(s => enabledMap[s.id]).map(s => ({
|
||||||
statId: s.id,
|
statId: s.id,
|
||||||
required: requiredMap[s.id],
|
required: requiredMap[s.id],
|
||||||
acquired: acquiredMap[s.id],
|
acquired: acquiredMap[s.id],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const {scope} = getScopeContext();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="stat-input">
|
<div class="stat-input">
|
||||||
{#each $scope.stats as stat (stat.id)}
|
{#each actualScope.stats as stat (stat.id)}
|
||||||
{#if enabledMap[stat.id] != null}
|
{#if enabledMap[stat.id] != null}
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
{#if !noToggle}
|
{#if !noToggle}
|
||||||
|
|||||||
@@ -1,34 +1,68 @@
|
|||||||
<script lang="ts">
|
<script lang="ts" context="module">
|
||||||
import Status from "$lib/models/status";
|
const STATUS_ENTRIES: [Status, IconName][] = Object.entries(STATUS_ICONS).map(e => [parseInt(e[0]) as Status, e[1]])
|
||||||
|
|
||||||
export let status: Status;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<select class={`sc-${status}`} bind:value={status}>
|
<script lang="ts">
|
||||||
<option class="sc-0" value={Status.Blocked}>Blocked</option>
|
import type Status from "$lib/models/status";
|
||||||
<option class="sc-1" value={Status.Available}>Available</option>
|
import { STATUS_NAMES } from "$lib/models/status";
|
||||||
<option class="sc-2" value={Status.Background}>Background</option>
|
import { STATUS_ICONS } from "../common/StatusIcon.svelte";
|
||||||
<option class="sc-3" value={Status.Active}>Active</option>
|
import Icon, { type IconName } from "../layout/Icon.svelte";
|
||||||
<option class="sc-4" value={Status.Completed}>Completed</option>
|
|
||||||
<option class="sc-5" value={Status.Failed}>Failed</option>
|
export let status: Status;
|
||||||
<option class="sc-6" value={Status.Dropped}>Dropped</option>
|
|
||||||
</select>
|
console.log(STATUS_ENTRIES, status)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="status-select">
|
||||||
|
{#each STATUS_ENTRIES as [id, iconName] (id)}
|
||||||
|
<div on:click={() => {status = id}} class="option sc-{id}" class:selected={status === id}>
|
||||||
|
<Icon name={iconName} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<div class="status-name">{STATUS_NAMES[status]}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
@import "../../css/colors"
|
@import "../../css/colors"
|
||||||
|
|
||||||
.sc-0
|
div.status-select
|
||||||
color: $sc-0 !important
|
background-color: $color-entryhalf
|
||||||
.sc-1
|
display: flex
|
||||||
color: $sc-1 !important
|
flex-direction: row
|
||||||
.sc-2
|
margin-bottom: 0.8em
|
||||||
color: $sc-2 !important
|
margin-top: 0.2em
|
||||||
.sc-3
|
|
||||||
color: $sc-3 !important
|
div.status-name
|
||||||
.sc-4
|
margin-left: auto
|
||||||
color: $sc-4 !important
|
margin-top: 0.275em
|
||||||
.sc-5
|
margin-right: 1ch
|
||||||
color: $sc-5 !important
|
color: $color-entry7
|
||||||
.sc-6
|
|
||||||
color: $sc-6 !important
|
div.option
|
||||||
|
padding: 0.2em 0.5ch
|
||||||
|
font-size: 1.175em
|
||||||
|
color: $color-entry2
|
||||||
|
cursor: pointer
|
||||||
|
line-height: 1em
|
||||||
|
|
||||||
|
&.sc-4, &.sc-2
|
||||||
|
border-left: 1px solid $color-entry1
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color: $color-entry3
|
||||||
|
|
||||||
|
&.selected.sc-0
|
||||||
|
color: $sc-0 !important
|
||||||
|
&.selected.sc-1
|
||||||
|
color: $sc-1 !important
|
||||||
|
&.selected.sc-2
|
||||||
|
color: $sc-2 !important
|
||||||
|
&.selected.sc-3
|
||||||
|
color: $sc-3 !important
|
||||||
|
&.selected.sc-4
|
||||||
|
color: $sc-4 !important
|
||||||
|
&.selected.sc-5
|
||||||
|
color: $sc-5 !important
|
||||||
|
&.selected.sc-6
|
||||||
|
color: $sc-6 !important
|
||||||
</style>
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { formatFormTime } from "$lib/utils/date";
|
import { formatFormTime } from "$lib/utils/date";
|
||||||
import parseInterval from "$lib/utils/timeinterval";
|
import parseInterval, { morningInterval } from "$lib/utils/timeinterval";
|
||||||
|
|
||||||
export let from: string;
|
export let from: string;
|
||||||
export let to: string;
|
export let to: string;
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (intervalName !== "specific_dates") {
|
if (intervalName !== "specific_dates") {
|
||||||
const int = parseInterval(intervalName, openDate);
|
const int = morningInterval(parseInterval(intervalName, openDate));
|
||||||
from = formatFormTime(int.min);
|
from = formatFormTime(int.min);
|
||||||
to = formatFormTime(int.max);
|
to = formatFormTime(int.max);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,10 @@
|
|||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
div.desktop
|
div.desktop
|
||||||
@media screen and (max-width: 800px)
|
@media screen and (max-width: 749px)
|
||||||
display: none
|
display: none
|
||||||
|
|
||||||
div.mobile
|
div.mobile
|
||||||
@media screen and (min-width: 800px)
|
@media screen and (min-width: 750px)
|
||||||
display: none
|
display: none
|
||||||
</style>
|
</style>
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { TimeInterval } from "$lib/models/common";
|
||||||
|
import parseInterval, { datesOf } from "$lib/utils/timeinterval";
|
||||||
import { getItemListContext } from "../contexts/ItemListContext.svelte";
|
import { getItemListContext } from "../contexts/ItemListContext.svelte";
|
||||||
|
import { getTimeContext } from "../contexts/TimeContext.svelte";
|
||||||
import HistoryGroupSection from "./HistoryGroupSection.svelte";
|
import HistoryGroupSection from "./HistoryGroupSection.svelte";
|
||||||
|
|
||||||
|
export let intervalString: string;
|
||||||
|
|
||||||
const {groups, items} = getItemListContext();
|
const {groups, items} = getItemListContext();
|
||||||
|
const {now} = getTimeContext();
|
||||||
|
|
||||||
|
let interval: TimeInterval<string>
|
||||||
|
$: interval = datesOf(parseInterval(intervalString, $now));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each $groups as group (group.label)}
|
{#each $groups as group (group.label)}
|
||||||
<HistoryGroupSection group={group} items={$items} />
|
{#if (group.label >= interval.min && group.label <= interval.max)}
|
||||||
|
<HistoryGroupSection group={group} items={$items} />
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
import Icon, { type IconName } from "../layout/Icon.svelte";
|
||||||
|
|
||||||
|
export let name: IconName;
|
||||||
|
export let value: boolean;
|
||||||
|
export let inverse: boolean = false;
|
||||||
|
|
||||||
|
const dispatcher = createEventDispatcher();
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
value = !value;
|
||||||
|
dispatcher("click");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span on:click={onClick} class="toggle-icon" class:checked={value !== inverse}>
|
||||||
|
<Icon name={name} />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
@import "../../css/colors"
|
||||||
|
|
||||||
|
.toggle-icon
|
||||||
|
font-size: 0.666em
|
||||||
|
color: $color-entry1
|
||||||
|
user-select: none
|
||||||
|
-moz-user-select: none
|
||||||
|
-webkit-user-select: none
|
||||||
|
|
||||||
|
&.checked
|
||||||
|
color: $color-entry7
|
||||||
|
</style>
|
||||||
@@ -14,6 +14,6 @@
|
|||||||
flex-basis: 50
|
flex-basis: 50
|
||||||
|
|
||||||
&.hideMobile
|
&.hideMobile
|
||||||
@media screen and (max-width: 800px)
|
@media screen and (max-width: 749px)
|
||||||
display: none
|
display: none
|
||||||
</style>
|
</style>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
&.fullwidth
|
&.fullwidth
|
||||||
width: 100%
|
width: 100%
|
||||||
|
|
||||||
@media screen and (max-width: 800px)
|
@media screen and (max-width: 749px)
|
||||||
width: 100%
|
width: 100%
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,9 @@
|
|||||||
import { faLightbulb } from "@fortawesome/free-solid-svg-icons/faLightbulb";
|
import { faLightbulb } from "@fortawesome/free-solid-svg-icons/faLightbulb";
|
||||||
import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight";
|
import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight";
|
||||||
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
|
||||||
|
import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash";
|
||||||
|
import { faCheckToSlot } from "@fortawesome/free-solid-svg-icons/faCheckToSlot";
|
||||||
|
import { faEye } from "@fortawesome/free-solid-svg-icons/faEye";
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
"clock": faClock,
|
"clock": faClock,
|
||||||
@@ -62,6 +65,9 @@
|
|||||||
"search": faSearch,
|
"search": faSearch,
|
||||||
"chevron_right": faChevronRight,
|
"chevron_right": faChevronRight,
|
||||||
"chevron_down": faChevronDown,
|
"chevron_down": faChevronDown,
|
||||||
|
"trash": faTrash,
|
||||||
|
"check_slot": faCheckToSlot,
|
||||||
|
"eye": faEye,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IconName = keyof typeof icons;
|
export type IconName = keyof typeof icons;
|
||||||
|
|||||||
@@ -51,21 +51,6 @@
|
|||||||
div.header
|
div.header
|
||||||
display: flex
|
display: flex
|
||||||
|
|
||||||
h2
|
|
||||||
font-size: 1.25em
|
|
||||||
margin: 0
|
|
||||||
color: $color-entry10
|
|
||||||
|
|
||||||
span.sub
|
|
||||||
color: $color-entry5
|
|
||||||
|
|
||||||
&.big
|
|
||||||
margin-top: 1em
|
|
||||||
font-size: 2em
|
|
||||||
|
|
||||||
&.small
|
|
||||||
font-size: 1em
|
|
||||||
|
|
||||||
div.right
|
div.right
|
||||||
color: $color-entry2-transparent
|
color: $color-entry2-transparent
|
||||||
margin-left: auto
|
margin-left: auto
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let centered = false;
|
export let centered = false;
|
||||||
|
export let vertical = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="option-row" class:centered>
|
<div class="option-row" class:vertical class:centered>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -10,10 +11,18 @@
|
|||||||
div.option-row {
|
div.option-row {
|
||||||
margin-left: -0.4ch;
|
margin-left: -0.4ch;
|
||||||
margin-right: -0.4ch;
|
margin-right: -0.4ch;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.option-row.centered {
|
div.option-row.centered {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-bottom: 0.5em;
|
padding-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.vertical {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import shortestDecimal from "$lib/utils/numbers";
|
||||||
|
|
||||||
|
|
||||||
export let offset: number;
|
export let offset: number;
|
||||||
|
|
||||||
let value: string;
|
let value: string;
|
||||||
let negative: boolean
|
let negative: boolean
|
||||||
$: value = (offset > 0) ? `+${offset.toFixed()}` : offset.toFixed();
|
$: value = (offset > 0) ? `+${shortestDecimal(offset)}` : shortestDecimal(offset);
|
||||||
$: negative = offset < 0
|
$: negative = offset < 0
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,10 @@
|
|||||||
export let noProgress = false;
|
export let noProgress = false;
|
||||||
export let icon: IconName = null;
|
export let icon: IconName = null;
|
||||||
export let status: Status = null;
|
export let status: Status = null;
|
||||||
|
export let anchor: string = "";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sl3-entry">
|
<div id={anchor} class="sl3-entry">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="entry-icon">
|
<div class="entry-icon">
|
||||||
{#if icon != null}
|
{#if icon != null}
|
||||||
@@ -44,33 +45,20 @@
|
|||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
margin-right: 0.5ch
|
margin-right: 0.5ch
|
||||||
font-size: 1em
|
font-size: 1em
|
||||||
margin-top: 0.225em
|
margin-top: 0.15em
|
||||||
|
|
||||||
&:empty
|
&:empty
|
||||||
display: none
|
display: none
|
||||||
|
|
||||||
div.header
|
div.header
|
||||||
display: flex
|
display: flex
|
||||||
|
line-height: 1.25em
|
||||||
h2
|
|
||||||
font-size: 1.25em
|
|
||||||
margin: 0
|
|
||||||
color: $color-entry10
|
|
||||||
|
|
||||||
span.sub
|
|
||||||
color: $color-entry5
|
|
||||||
|
|
||||||
&.big
|
|
||||||
margin-top: 1em
|
|
||||||
font-size: 2em
|
|
||||||
|
|
||||||
&.small
|
|
||||||
font-size: 1em
|
|
||||||
|
|
||||||
div.right
|
div.right
|
||||||
color: $color-entry2-transparent
|
color: $color-entry2-transparent
|
||||||
margin-left: auto
|
margin-left: auto
|
||||||
margin-top: auto
|
margin-top: auto
|
||||||
|
margin-bottom: 0
|
||||||
|
|
||||||
&.noProgress
|
&.noProgress
|
||||||
margin-top: -0.25em
|
margin-top: -0.25em
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
div.sub-section
|
div.sub-section
|
||||||
margin: 0.5em 0.4ch
|
margin: 0.5em 0.4ch
|
||||||
padding: 0.33em 0.75ch
|
padding: 0.33em 0.75ch
|
||||||
|
padding-bottom: 0.25em
|
||||||
background: $color-entry1-transparent
|
background: $color-entry1-transparent
|
||||||
border-radius: 0.125em
|
border-radius: 0.125em
|
||||||
border-bottom-right-radius: 0.5em
|
border-bottom-right-radius: 0.5em
|
||||||
@@ -54,7 +55,6 @@
|
|||||||
opacity: 0.6
|
opacity: 0.6
|
||||||
margin-right: 1.5ch
|
margin-right: 1.5ch
|
||||||
font-size: 0.75em
|
font-size: 0.75em
|
||||||
margin-top: auto
|
|
||||||
|
|
||||||
span.event
|
span.event
|
||||||
font-size: 1.33em
|
font-size: 1.33em
|
||||||
@@ -64,7 +64,8 @@
|
|||||||
display: none
|
display: none
|
||||||
|
|
||||||
&.blankEvent
|
&.blankEvent
|
||||||
margin-right: 0.5ch
|
margin-top: 0.35em
|
||||||
|
margin-right: 0.75ch
|
||||||
|
|
||||||
span.event
|
span.event
|
||||||
display: none
|
display: none
|
||||||
@@ -78,6 +79,6 @@
|
|||||||
margin-top: auto
|
margin-top: auto
|
||||||
|
|
||||||
&.noProgress
|
&.noProgress
|
||||||
margin-top: -0.25em
|
margin-top: 0em
|
||||||
margin-bottom: auto
|
margin-bottom: auto
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type Item from "$lib/models/item";
|
||||||
|
import Amount from "../common/Amount.svelte";
|
||||||
|
import AmountRow from "../common/AmountRow.svelte";
|
||||||
|
import { getScopeContext } from "../contexts/ScopeContext.svelte";
|
||||||
|
|
||||||
|
export let items: Item[];
|
||||||
|
export let totalAcquired: number;
|
||||||
|
export let aggregateRequired: number;
|
||||||
|
|
||||||
|
const {scope} = getScopeContext();
|
||||||
|
|
||||||
|
let amounts: {name: string, amount: number}[] = [];
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const statMap: Record<number, number> = {};
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
for (const stat of item.stats) {
|
||||||
|
statMap[stat.id] = (statMap[stat.id] || 0) + stat.acquired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
amounts = [];
|
||||||
|
for (const stat of $scope.stats) {
|
||||||
|
if (!!statMap[stat.id]) {
|
||||||
|
amounts.push({name: stat.name, amount: statMap[stat.id]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<AmountRow>
|
||||||
|
{#each amounts as {name, amount} (name)}
|
||||||
|
<Amount label={name} value={amount} />
|
||||||
|
{/each}
|
||||||
|
<Amount right label="" dark value={totalAcquired} target={aggregateRequired} />
|
||||||
|
</AmountRow>
|
||||||
@@ -8,58 +8,83 @@
|
|||||||
import Amount from "../common/Amount.svelte";
|
import Amount from "../common/Amount.svelte";
|
||||||
import AmountRow from "../common/AmountRow.svelte";
|
import AmountRow from "../common/AmountRow.svelte";
|
||||||
import type { IconName } from "../layout/Icon.svelte";
|
import type { IconName } from "../layout/Icon.svelte";
|
||||||
|
import Icon from "../layout/Icon.svelte";
|
||||||
|
import { formatTime } from "$lib/utils/date";
|
||||||
|
import RequirementReference from "./RequirementReference.svelte";
|
||||||
|
import ItemTimeInfo from "./ItemTimeInfo.svelte";
|
||||||
|
|
||||||
export let item: Item;
|
export let item: Item;
|
||||||
export let event: "created" | "scheduled" | "acquired" | "none" = "none";
|
export let event: "created" | "scheduled" | "acquired" | "none" = "none";
|
||||||
export let compact = false;
|
export let compact = false;
|
||||||
|
export let showAcquiredTime = false;
|
||||||
|
export let hideStats = false;
|
||||||
|
|
||||||
let icon: IconName
|
let icon: IconName
|
||||||
let status: Status
|
let status: Status
|
||||||
|
let eventText: string
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
|
icon = void(0);
|
||||||
|
|
||||||
if (event === "none" || event === "acquired") {
|
if (event === "none" || event === "acquired") {
|
||||||
if (item.acquiredTime) {
|
if (item.acquiredTime) {
|
||||||
icon = "check";
|
icon = "check";
|
||||||
status = Status.Completed;
|
status = Status.Completed;
|
||||||
|
eventText = (showAcquiredTime || event === "acquired") ? formatTime(item.acquiredTime) : void(0)
|
||||||
}
|
}
|
||||||
} else if (event === "scheduled") {
|
} else if (event === "scheduled") {
|
||||||
if (item.acquiredTime) {
|
if (item.acquiredTime) {
|
||||||
icon = "hourglass";
|
icon = "hourglass";
|
||||||
status = Status.Blocked;
|
status = Status.Blocked;
|
||||||
|
eventText = "scheduled"
|
||||||
}
|
}
|
||||||
} else if (event === "created") {
|
} else if (event === "created") {
|
||||||
icon = "lightbulb";
|
icon = "lightbulb";
|
||||||
status = Status.Available;
|
status = Status.Available;
|
||||||
|
eventText = formatTime(item.createdTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SubSection small noProgress title={item.name} icon={icon} status={status} event={event}>
|
<SubSection small noProgress title={item.name} icon={icon} status={status} event={eventText}>
|
||||||
<OptionsRow slot="right">
|
<OptionsRow slot="right">
|
||||||
{#if item.acquiredTime == null}
|
{#if item.acquiredTime == null}
|
||||||
<Option open={{name: "item.acquire", item}} color="green">Acquire</Option>
|
<Option open={{name: "item.acquire", item}} color="green"><Icon name="check_slot" /></Option>
|
||||||
{/if}
|
{/if}
|
||||||
{#if !compact}
|
{#if !compact}
|
||||||
<Option open={{name: "item.edit", item}}>Edit</Option>
|
<Option open={{name: "item.edit", item}}><Icon name="pen" /></Option>
|
||||||
<Option open={{name: "item.delete", item}} color="red">Delete</Option>
|
<Option open={{name: "item.delete", item}} color="red"><Icon name="trash" /></Option>
|
||||||
{/if}
|
{/if}
|
||||||
</OptionsRow>
|
</OptionsRow>
|
||||||
|
{#if item.project != null}
|
||||||
|
<RequirementReference project={item.project} requirement={item.requirement} scopeId={item.scopeId} />
|
||||||
|
{:else}
|
||||||
|
<ItemTimeInfo item={item} />
|
||||||
|
{/if}
|
||||||
{#if !compact}
|
{#if !compact}
|
||||||
<Markdown source={item.description} />
|
<Markdown source={item.description} />
|
||||||
{/if}
|
{/if}
|
||||||
<AmountRow>
|
<AmountRow>
|
||||||
{#if item.acquiredTime != null}
|
{#if !hideStats}
|
||||||
{#each item.stats as stat (stat.id)}
|
{#if item.acquiredTime != null}
|
||||||
{#if stat.required > 0}
|
{#each item.stats as stat (stat.id)}
|
||||||
<Amount label={stat.name} value={stat.acquired} target={stat.required} />
|
{#if stat.required > 0}
|
||||||
|
<Amount label={stat.name} value={stat.acquired} target={stat.required} />
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{#if item.weightedRequired}
|
||||||
|
<Amount dark right label="" value={item.weightedAcquired} target={item.weightedRequired} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{:else}
|
||||||
{:else}
|
{#each item.stats as stat (stat.id)}
|
||||||
{#each item.stats as stat (stat.id)}
|
{#if stat.required > 0}
|
||||||
{#if stat.required > 0}
|
<Amount label={stat.name} value={stat.required} />
|
||||||
<Amount label={stat.name} value={stat.required} />
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{#if item.weightedRequired}
|
||||||
|
<Amount dark right label="" value={item.weightedRequired} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</AmountRow>
|
</AmountRow>
|
||||||
</SubSection>
|
</SubSection>
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type Item from "$lib/models/item";
|
||||||
|
import { formatPrettyTime } from "$lib/utils/date";
|
||||||
|
import { getTimeContext } from "../contexts/TimeContext.svelte";
|
||||||
|
|
||||||
|
export let item: Item
|
||||||
|
|
||||||
|
const {now} = getTimeContext();
|
||||||
|
|
||||||
|
let action = "create";
|
||||||
|
let colorClass = "";
|
||||||
|
let timeStr = "";
|
||||||
|
|
||||||
|
$: {
|
||||||
|
const ct = new Date(item.createdTime);
|
||||||
|
let t = ct;
|
||||||
|
|
||||||
|
if (item.acquiredTime != null) {
|
||||||
|
const at = new Date(item.acquiredTime);
|
||||||
|
t = at;
|
||||||
|
|
||||||
|
if (Math.abs(at.getTime() - ct.getTime()) < 300000) {
|
||||||
|
action = "Created/Acquired"
|
||||||
|
} else {
|
||||||
|
action = "Acquired"
|
||||||
|
}
|
||||||
|
colorClass = "acquired"
|
||||||
|
} else if (item.scheduledDate != null) {
|
||||||
|
t = new Date(item.scheduledDate);
|
||||||
|
action = "Scheduled for"
|
||||||
|
colorClass = "scheduled"
|
||||||
|
} else {
|
||||||
|
action = "Created"
|
||||||
|
colorClass = "created"
|
||||||
|
}
|
||||||
|
|
||||||
|
timeStr = formatPrettyTime(t, $now);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="item-time {colorClass}">
|
||||||
|
<span class="action">{action}</span>
|
||||||
|
<span class="timeStr">{timeStr}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
@import "../../css/colors"
|
||||||
|
|
||||||
|
div.item-time
|
||||||
|
font-size: 0.8em
|
||||||
|
|
||||||
|
color: $color-entry3
|
||||||
|
|
||||||
|
&.created
|
||||||
|
color: $sc-1-dim
|
||||||
|
&.acquired
|
||||||
|
color: $sc-4-dim
|
||||||
|
&.scheduled
|
||||||
|
color: $sc-0
|
||||||
|
|
||||||
|
> span.timeStr
|
||||||
|
font-weight: 800
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
import Option from "$lib/components/layout/Option.svelte";
|
import Option from "$lib/components/layout/Option.svelte";
|
||||||
import OptionsRow from "$lib/components/layout/OptionsRow.svelte";
|
import OptionsRow from "$lib/components/layout/OptionsRow.svelte";
|
||||||
import { getProjectContext } from "../contexts/ProjectContext.svelte";
|
import { getProjectContext } from "../contexts/ProjectContext.svelte";
|
||||||
|
import Icon from "../layout/Icon.svelte";
|
||||||
import RequirementEntry from "./RequirementSection.svelte";
|
import RequirementEntry from "./RequirementSection.svelte";
|
||||||
|
|
||||||
const {project} = getProjectContext();
|
const {project} = getProjectContext();
|
||||||
@@ -15,9 +16,9 @@
|
|||||||
<Progress alwaysSmooth titlePercentageOnly thinner gray count={$project.totalPlanned} target={$project.totalRequired} />
|
<Progress alwaysSmooth titlePercentageOnly thinner gray count={$project.totalPlanned} target={$project.totalRequired} />
|
||||||
<Markdown source={$project.description} />
|
<Markdown source={$project.description} />
|
||||||
<OptionsRow slot="right">
|
<OptionsRow slot="right">
|
||||||
<Option open={{name: "requirement.create", project: $project}}>Add Requirement</Option>
|
<Option open={{name: "requirement.create", project: $project}}><Icon name="plus" /></Option>
|
||||||
<Option open={{name: "project.edit", project: $project}}>Edit</Option>
|
<Option open={{name: "project.edit", project: $project}}><Icon name="pen" /></Option>
|
||||||
<Option open={{name: "project.delete", project: $project}} color="red">Delete</Option>
|
<Option open={{name: "project.delete", project: $project}} color="red"><Icon name="trash" /></Option>
|
||||||
</OptionsRow>
|
</OptionsRow>
|
||||||
{#each $project.requirements as requirement (requirement.id)}
|
{#each $project.requirements as requirement (requirement.id)}
|
||||||
<RequirementEntry requirement={requirement} />
|
<RequirementEntry requirement={requirement} />
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export interface IDAndName {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { projectPrettyId, scopePrettyId } from "$lib/utils/prettyIds";
|
||||||
|
import { getScopeContext } from "../contexts/ScopeContext.svelte";
|
||||||
|
import { getScopeListContext } from "../contexts/ScopeListContext.svelte";
|
||||||
|
|
||||||
|
export let project: IDAndName
|
||||||
|
export let requirement: IDAndName
|
||||||
|
export let scopeId: number
|
||||||
|
|
||||||
|
const {scope} = getScopeContext();
|
||||||
|
const {scopes} = getScopeListContext();
|
||||||
|
|
||||||
|
let projectLink: string
|
||||||
|
let requirementLink: string
|
||||||
|
|
||||||
|
$: {
|
||||||
|
let actualScope = $scope;
|
||||||
|
if (actualScope == null || actualScope.id !== scopeId) {
|
||||||
|
actualScope = $scopes.find(s => s.id === scopeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
projectLink = `/${scopePrettyId(actualScope)}/projects/${projectPrettyId(project)}`
|
||||||
|
requirementLink = `${projectLink}#${projectPrettyId(requirement)}`
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="reference">
|
||||||
|
<a href={projectLink}>{project.name}</a>
|
||||||
|
<span>></span>
|
||||||
|
<a href={requirementLink}>{requirement.name}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
@import "../../css/colors"
|
||||||
|
|
||||||
|
div.reference
|
||||||
|
font-size: 0.8em
|
||||||
|
|
||||||
|
> span
|
||||||
|
color: $color-entry3
|
||||||
|
|
||||||
|
> a
|
||||||
|
color: $color-entry5
|
||||||
|
text-decoration: none
|
||||||
|
line-height: 1em
|
||||||
|
transition: 250ms
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color: $color-entry8
|
||||||
|
</style>
|
||||||
@@ -9,18 +9,23 @@
|
|||||||
import LabeledProgressRow from "../common/LabeledProgressRow.svelte";
|
import LabeledProgressRow from "../common/LabeledProgressRow.svelte";
|
||||||
import { STATUS_ICONS } from "../common/StatusIcon.svelte";
|
import { STATUS_ICONS } from "../common/StatusIcon.svelte";
|
||||||
import ItemEntry from "./ItemSubSection.svelte";
|
import ItemEntry from "./ItemSubSection.svelte";
|
||||||
|
import Icon from "../layout/Icon.svelte";
|
||||||
|
import { projectPrettyId } from "$lib/utils/prettyIds";
|
||||||
|
import AmountRow from "../common/AmountRow.svelte";
|
||||||
|
import AggregateAmountRow from "./AggregateAmountRow.svelte";
|
||||||
|
|
||||||
export let requirement: Requirement;
|
export let requirement: Requirement;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div id={projectPrettyId(requirement)} />
|
||||||
<Section title={requirement.name} icon={STATUS_ICONS[requirement.status]} status={requirement.status}>
|
<Section title={requirement.name} icon={STATUS_ICONS[requirement.status]} status={requirement.status}>
|
||||||
<Progress alwaysSmooth titlePercentageOnly thin green status={requirement.status} count={requirement.totalAcquired} target={requirement.totalRequired} />
|
<Progress alwaysSmooth titlePercentageOnly thin green status={requirement.status} count={requirement.totalAcquired} target={requirement.totalRequired} />
|
||||||
<Progress alwaysSmooth titlePercentageOnly thinner gray count={requirement.totalPlanned} target={requirement.totalRequired} />
|
<Progress alwaysSmooth titlePercentageOnly thinner gray count={requirement.totalPlanned} target={requirement.totalRequired} />
|
||||||
<Markdown source={requirement.description} />
|
<Markdown source={requirement.description} />
|
||||||
<OptionsRow slot="right">
|
<OptionsRow slot="right">
|
||||||
<Option open={{name: "item.create", requirement}}>Add Item</Option>
|
<Option open={{name: "item.create", requirement}}><Icon name="plus" /></Option>
|
||||||
<Option open={{name: "requirement.edit", requirement}}>Edit</Option>
|
<Option open={{name: "requirement.edit", requirement}}><Icon name="pen" /></Option>
|
||||||
<Option open={{name: "requirement.delete", requirement}} color="red">Delete</Option>
|
<Option open={{name: "requirement.delete", requirement}} color="red"><Icon name="trash" /></Option>
|
||||||
</OptionsRow>
|
</OptionsRow>
|
||||||
<LabeledProgressRow>
|
<LabeledProgressRow>
|
||||||
{#each requirement.stats as stat (stat.id)}
|
{#each requirement.stats as stat (stat.id)}
|
||||||
@@ -32,4 +37,7 @@
|
|||||||
{#each requirement.items as item (item.id)}
|
{#each requirement.items as item (item.id)}
|
||||||
<ItemEntry item={item} />
|
<ItemEntry item={item} />
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if requirement.aggregateRequired > 0}
|
||||||
|
<AggregateAmountRow items={requirement.items} totalAcquired={requirement.totalAcquired} aggregateRequired={requirement.aggregateRequired} />
|
||||||
|
{/if}
|
||||||
</Section>
|
</Section>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getItemMultiListContext } from "../contexts/ItemMultiListContext.svelte";
|
||||||
|
import Row from "../layout/Row.svelte";
|
||||||
|
import ItemSubSection from "../project/ItemSubSection.svelte";
|
||||||
|
|
||||||
|
export let key: string;
|
||||||
|
export let title: string;
|
||||||
|
export let showAcquiredTime: boolean = false;
|
||||||
|
|
||||||
|
const {lists} = getItemMultiListContext();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{#if ($lists[key]||[]).length > 0}
|
||||||
|
<Row title={title}>
|
||||||
|
{#each $lists[key] as item (item.id)}
|
||||||
|
<ItemSubSection showAcquiredTime={showAcquiredTime} item={item} />
|
||||||
|
{/each}
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
import ItemSubSection from "../project/ItemSubSection.svelte";
|
import ItemSubSection from "../project/ItemSubSection.svelte";
|
||||||
|
|
||||||
export let sprint: Sprint
|
export let sprint: Sprint
|
||||||
|
export let compact: boolean = false;
|
||||||
|
|
||||||
let timeRange: [Date, Date];
|
let timeRange: [Date, Date];
|
||||||
|
|
||||||
@@ -34,12 +35,18 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if sprint.kind === SprintKind.Items}
|
{#if sprint.kind === SprintKind.Items}
|
||||||
{#each (sprint.items||[]) as item (item.id)}
|
{#each (sprint.items||[]) as item (item.id)}
|
||||||
{#if !item.acquiredTime}
|
{#if !compact || !item.acquiredTime}
|
||||||
<ItemSubSection compact item={item} />
|
<ItemSubSection compact={compact} hideStats={compact} item={item} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<AmountRow>
|
{#if compact}
|
||||||
<Amount label="Acquired Items" value={(sprint.items||[]).length} />
|
<AmountRow>
|
||||||
</AmountRow>
|
<Amount label="Acquired Items" value={(sprint.items||[]).length} />
|
||||||
|
</AmountRow>
|
||||||
|
{:else}
|
||||||
|
{#each (sprint.items||[]) as item (item.id)}
|
||||||
|
<ItemSubSection compact item={item} />
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -1,27 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from "$app/navigation";
|
|
||||||
import type Sprint from "$lib/models/sprint";
|
import type Sprint from "$lib/models/sprint";
|
||||||
|
|
||||||
import Option from "../layout/Option.svelte";
|
import Option from "../layout/Option.svelte";
|
||||||
import OptionsRow from "../layout/OptionsRow.svelte";
|
import OptionsRow from "../layout/OptionsRow.svelte";
|
||||||
import SprintBody from "./SprintBody.svelte";
|
import SprintBody from "./SprintBody.svelte";
|
||||||
import { projectPrettyId, scopePrettyId } from "$lib/utils/prettyIds";
|
import { projectPrettyId } from "$lib/utils/prettyIds";
|
||||||
import { getScopeContext } from "../contexts/ScopeContext.svelte";
|
|
||||||
import Section from "../layout/Section.svelte";
|
import Section from "../layout/Section.svelte";
|
||||||
|
import Icon from "../layout/Icon.svelte";
|
||||||
|
|
||||||
export let sprint: Sprint;
|
export let sprint: Sprint;
|
||||||
const {scope} = getScopeContext();
|
|
||||||
|
|
||||||
function gotoSprint() {
|
|
||||||
goto(`/${scopePrettyId($scope)}/sprints#${projectPrettyId(sprint)}`)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Section noProgress title={sprint.name}>
|
<Section anchor={projectPrettyId(sprint)} noProgress title={sprint.name}>
|
||||||
<OptionsRow slot="right">
|
<OptionsRow slot="right">
|
||||||
<Option on:click={gotoSprint}>View</Option>
|
<Option open={{name: "sprint.edit", sprint}}><Icon name="pen" /></Option>
|
||||||
<Option open={{name: "sprint.edit", sprint}}>Edit</Option>
|
<Option open={{name: "sprint.delete", sprint}} color="red"><Icon name="trash" /></Option>
|
||||||
<Option open={{name: "sprint.delete", sprint}} color="red">Delete</Option>
|
|
||||||
</OptionsRow>
|
</OptionsRow>
|
||||||
<SprintBody sprint={sprint} />
|
<SprintBody sprint={sprint} />
|
||||||
</Section>
|
</Section>
|
||||||
@@ -8,20 +8,28 @@
|
|||||||
import SprintBody from "./SprintBody.svelte";
|
import SprintBody from "./SprintBody.svelte";
|
||||||
import { projectPrettyId, scopePrettyId } from "$lib/utils/prettyIds";
|
import { projectPrettyId, scopePrettyId } from "$lib/utils/prettyIds";
|
||||||
import { getScopeContext } from "../contexts/ScopeContext.svelte";
|
import { getScopeContext } from "../contexts/ScopeContext.svelte";
|
||||||
|
import { getScopeListContext } from "../contexts/ScopeListContext.svelte";
|
||||||
|
import Icon from "../layout/Icon.svelte";
|
||||||
|
|
||||||
export let sprint: Sprint;
|
export let sprint: Sprint;
|
||||||
const {scope} = getScopeContext();
|
const {scope, lastHistoryRange} = getScopeContext();
|
||||||
|
const {scopes} = getScopeListContext();
|
||||||
|
|
||||||
function gotoSprint() {
|
function gotoSprint() {
|
||||||
goto(`/${scopePrettyId($scope)}/sprints#${projectPrettyId(sprint)}`)
|
let sc = $scope;
|
||||||
|
if (sc.id !== sprint.scopeId) {
|
||||||
|
sc = $scopes.find(s => s.id === sprint.scopeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
goto(`/${scopePrettyId(sc)}/sprints/${$lastHistoryRange}#${projectPrettyId(sprint)}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SubSection noProgress title={sprint.name}>
|
<SubSection noProgress title={sprint.name}>
|
||||||
<OptionsRow slot="right">
|
<OptionsRow slot="right">
|
||||||
<Option on:click={gotoSprint}>View</Option>
|
<Option on:click={gotoSprint}><Icon name="eye" /></Option>
|
||||||
<Option open={{name: "sprint.edit", sprint}}>Edit</Option>
|
<Option open={{name: "sprint.edit", sprint}}><Icon name="pen" /></Option>
|
||||||
<Option open={{name: "sprint.delete", sprint}} color="red">Delete</Option>
|
<Option open={{name: "sprint.delete", sprint}} color="red"><Icon name="trash" /></Option>
|
||||||
</OptionsRow>
|
</OptionsRow>
|
||||||
<SprintBody sprint={sprint} />
|
<SprintBody compact sprint={sprint} />
|
||||||
</SubSection>
|
</SubSection>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import Amount from "../common/Amount.svelte";
|
import Amount from "../common/Amount.svelte";
|
||||||
import AmountRow from "../common/AmountRow.svelte";
|
import AmountRow from "../common/AmountRow.svelte";
|
||||||
import Markdown from "../common/Markdown.svelte";
|
import Markdown from "../common/Markdown.svelte";
|
||||||
|
import Icon from "../layout/Icon.svelte";
|
||||||
import Option from "../layout/Option.svelte";
|
import Option from "../layout/Option.svelte";
|
||||||
import OptionsRow from "../layout/OptionsRow.svelte";
|
import OptionsRow from "../layout/OptionsRow.svelte";
|
||||||
import SubSection from "../layout/SubSection.svelte";
|
import SubSection from "../layout/SubSection.svelte";
|
||||||
@@ -12,8 +13,8 @@
|
|||||||
|
|
||||||
<SubSection noProgress title={stat.name}>
|
<SubSection noProgress title={stat.name}>
|
||||||
<OptionsRow slot="right">
|
<OptionsRow slot="right">
|
||||||
<Option open={{name: "stat.edit", stat}}>Edit</Option>
|
<Option open={{name: "stat.edit", stat}}><Icon name="pen" /></Option>
|
||||||
<Option open={{name: "stat.delete", stat}} color="red">Delete</Option>
|
<Option open={{name: "stat.delete", stat}} color="red"><Icon name="trash" /></Option>
|
||||||
</OptionsRow>
|
</OptionsRow>
|
||||||
<Markdown source={stat.description} />
|
<Markdown source={stat.description} />
|
||||||
{#if stat.weight !== 1}
|
{#if stat.weight !== 1}
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ $opacity-entry6: 0.80
|
|||||||
$sc-0: #78c9ff
|
$sc-0: #78c9ff
|
||||||
$sc-0-dark: #34424b
|
$sc-0-dark: #34424b
|
||||||
$sc-1: #e7e55e
|
$sc-1: #e7e55e
|
||||||
|
$sc-1-dim: #a3a352
|
||||||
$sc-2: #e7a75e
|
$sc-2: #e7a75e
|
||||||
$sc-3: $color-entry12
|
$sc-3: $color-entry12
|
||||||
$sc-4: #78ff78
|
$sc-4: #78ff78
|
||||||
|
$sc-4-dim: #549454
|
||||||
$sc-4-dark: #2f422f
|
$sc-4-dark: #2f422f
|
||||||
$sc-5: #c94339
|
$sc-5: #c94339
|
||||||
$sc-6: #e75ed0
|
$sc-6: #e75ed0
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
import { sl3 } from "$lib/clients/sl3";
|
import { sl3 } from "$lib/clients/sl3";
|
||||||
import Modal from "$lib/components/common/Modal.svelte";
|
import Modal from "$lib/components/common/Modal.svelte";
|
||||||
import ModalBody from "$lib/components/common/ModalBody.svelte";
|
import ModalBody from "$lib/components/common/ModalBody.svelte";
|
||||||
import { getItemListContext } from "$lib/components/contexts/ItemListContext.svelte";
|
import { getItemListContext } from "$lib/components/contexts/ItemListContext.svelte";
|
||||||
|
import { getItemMultiListContext } from "$lib/components/contexts/ItemMultiListContext.svelte";
|
||||||
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
|
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
|
||||||
import { getProjectContext } from "$lib/components/contexts/ProjectContext.svelte";
|
import { getProjectContext } from "$lib/components/contexts/ProjectContext.svelte";
|
||||||
import { getProjectListContext } from "$lib/components/contexts/ProjectListContext.svelte";
|
import { getProjectListContext } from "$lib/components/contexts/ProjectListContext.svelte";
|
||||||
@@ -26,6 +27,7 @@ import { getItemListContext } from "$lib/components/contexts/ItemListContext.sve
|
|||||||
const {reloadProjectList} = getProjectListContext();
|
const {reloadProjectList} = getProjectListContext();
|
||||||
const {reloadSprintList} = getSprintListContext();
|
const {reloadSprintList} = getSprintListContext();
|
||||||
const {reloadItemList} = getItemListContext();
|
const {reloadItemList} = getItemListContext();
|
||||||
|
const {reloadItemLists} = getItemMultiListContext();
|
||||||
|
|
||||||
let endpoint: string;
|
let endpoint: string;
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ import { getItemListContext } from "$lib/components/contexts/ItemListContext.sve
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "item.delete":
|
case "item.delete":
|
||||||
init(`items/${$currentModal.item.id}`)
|
init(`items/${$currentModal.item.id}`, [], $currentModal.item.scopeId)
|
||||||
noun = "Item";
|
noun = "Item";
|
||||||
name = $currentModal.item.name;
|
name = $currentModal.item.name;
|
||||||
break;
|
break;
|
||||||
@@ -62,7 +64,7 @@ import { getItemListContext } from "$lib/components/contexts/ItemListContext.sve
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "sprint.delete":
|
case "sprint.delete":
|
||||||
init(`sprints/${$currentModal.sprint.id}`)
|
init(`sprints/${$currentModal.sprint.id}`, [], $currentModal.sprint.scopeId)
|
||||||
noun = "Sprint";
|
noun = "Sprint";
|
||||||
name = $currentModal.sprint.name;
|
name = $currentModal.sprint.name;
|
||||||
break;
|
break;
|
||||||
@@ -72,7 +74,7 @@ import { getItemListContext } from "$lib/components/contexts/ItemListContext.sve
|
|||||||
$currentModal.requirement.items.map(i => ({
|
$currentModal.requirement.items.map(i => ({
|
||||||
op: "Detach item",
|
op: "Detach item",
|
||||||
name: i.name,
|
name: i.name,
|
||||||
}))
|
})),
|
||||||
);
|
);
|
||||||
noun = "Requirement";
|
noun = "Requirement";
|
||||||
name = $currentModal.requirement.name;
|
name = $currentModal.requirement.name;
|
||||||
@@ -100,8 +102,13 @@ import { getItemListContext } from "$lib/components/contexts/ItemListContext.sve
|
|||||||
show = false;
|
show = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(newEndpoint: string, newSideEffects: DeletionSideEffect[] = []) {
|
function init(newEndpoint: string, newSideEffects: DeletionSideEffect[] = [], overrideScopeId?: number) {
|
||||||
endpoint = `scopes/${$scope.id}/${newEndpoint}`;
|
let scopeId = $scope.id;
|
||||||
|
if (overrideScopeId != null) {
|
||||||
|
scopeId = overrideScopeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint = `scopes/${scopeId}/${newEndpoint}`;
|
||||||
if (endpoint.endsWith("/")) {
|
if (endpoint.endsWith("/")) {
|
||||||
endpoint = endpoint.slice(0, -1);
|
endpoint = endpoint.slice(0, -1);
|
||||||
}
|
}
|
||||||
@@ -131,6 +138,7 @@ import { getItemListContext } from "$lib/components/contexts/ItemListContext.sve
|
|||||||
await reloadProject();
|
await reloadProject();
|
||||||
|
|
||||||
if (noun === "Stat") {
|
if (noun === "Stat") {
|
||||||
|
reloadItemLists();
|
||||||
deleteStat((res as {stat: Stat}).stat);
|
deleteStat((res as {stat: Stat}).stat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +147,7 @@ import { getItemListContext } from "$lib/components/contexts/ItemListContext.sve
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (noun === "Item") {
|
if (noun === "Item") {
|
||||||
|
reloadItemLists();
|
||||||
await reloadItemList();
|
await reloadItemList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,24 +4,24 @@
|
|||||||
import Modal from "$lib/components/common/Modal.svelte";
|
import Modal from "$lib/components/common/Modal.svelte";
|
||||||
import ModalBody from "$lib/components/common/ModalBody.svelte";
|
import ModalBody from "$lib/components/common/ModalBody.svelte";
|
||||||
import { getItemListContext } from "$lib/components/contexts/ItemListContext.svelte";
|
import { getItemListContext } from "$lib/components/contexts/ItemListContext.svelte";
|
||||||
|
import { getItemMultiListContext } from "$lib/components/contexts/ItemMultiListContext.svelte";
|
||||||
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
|
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
|
||||||
import { getProjectContext } from "$lib/components/contexts/ProjectContext.svelte";
|
import { getProjectContext } from "$lib/components/contexts/ProjectContext.svelte";
|
||||||
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte";
|
||||||
import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte";
|
|
||||||
import AcquiredTimeInput from "$lib/components/controls/AcquiredTimeInput.svelte";
|
import AcquiredTimeInput from "$lib/components/controls/AcquiredTimeInput.svelte";
|
||||||
import StatInput from "$lib/components/controls/StatInput.svelte";
|
import StatInput from "$lib/components/controls/StatInput.svelte";
|
||||||
import type Item from "$lib/models/item";
|
import type Item from "$lib/models/item";
|
||||||
import type { ItemInput } from "$lib/models/item";
|
import type { ItemInput } from "$lib/models/item";
|
||||||
import type Scope from "$lib/models/scope";
|
|
||||||
|
|
||||||
const {currentModal, closeModal} = getModalContext();
|
const {currentModal, closeModal} = getModalContext();
|
||||||
const {scope} = getScopeContext();
|
|
||||||
const {reloadProject} = getProjectContext();
|
const {reloadProject} = getProjectContext();
|
||||||
const {reloadItemList} = getItemListContext();
|
const {reloadItemList} = getItemListContext();
|
||||||
const {reloadSprintList} = getSprintListContext();
|
const {reloadSprintList} = getSprintListContext();
|
||||||
|
const {reloadItemLists} = getItemMultiListContext();
|
||||||
|
|
||||||
let item: Partial<ItemInput>
|
let item: Partial<ItemInput>
|
||||||
let itemId: number
|
let itemId: number
|
||||||
|
let scopeId: number
|
||||||
let requirementId: number
|
let requirementId: number
|
||||||
let openedDate: Date
|
let openedDate: Date
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
|
|
||||||
$: switch ($currentModal.name) {
|
$: switch ($currentModal.name) {
|
||||||
case "item.acquire":
|
case "item.acquire":
|
||||||
init($scope, $currentModal.item)
|
init($currentModal.item)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -40,7 +40,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
show = false;
|
show = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(scope: Scope, unaquiredItem: Item) {
|
function init(unaquiredItem: Item) {
|
||||||
openedDate = new Date();
|
openedDate = new Date();
|
||||||
|
|
||||||
item = {
|
item = {
|
||||||
@@ -50,6 +50,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
show = true;
|
show = true;
|
||||||
|
scopeId = unaquiredItem.scopeId;
|
||||||
itemId = unaquiredItem.id;
|
itemId = unaquiredItem.id;
|
||||||
requirementId = unaquiredItem.requirementId;
|
requirementId = unaquiredItem.requirementId;
|
||||||
}
|
}
|
||||||
@@ -59,7 +60,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sl3(fetch).updateItem($scope.id, itemId, {
|
await sl3(fetch).updateItem(scopeId, itemId, {
|
||||||
...item,
|
...item,
|
||||||
acquiredTime: new Date(item.acquiredTime).toISOString(),
|
acquiredTime: new Date(item.acquiredTime).toISOString(),
|
||||||
});
|
});
|
||||||
@@ -69,6 +70,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
await reloadProject();
|
await reloadProject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reloadItemLists();
|
||||||
await reloadItemList();
|
await reloadItemList();
|
||||||
await reloadSprintList();
|
await reloadSprintList();
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
<label for="description">Description</label>
|
<label for="description">Description</label>
|
||||||
<textarea name="description" bind:value={item.description} />
|
<textarea name="description" bind:value={item.description} />
|
||||||
<label for="stats">Stats</label>
|
<label for="stats">Stats</label>
|
||||||
<StatInput showAcquired hideUnseen noToggle bind:value={item.stats} />
|
<StatInput overrideScopeId={scopeId} showAcquired hideUnseen noToggle bind:value={item.stats} />
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
</form>
|
</form>
|
||||||
@@ -3,12 +3,15 @@
|
|||||||
|
|
||||||
import Modal from "$lib/components/common/Modal.svelte";
|
import Modal from "$lib/components/common/Modal.svelte";
|
||||||
import ModalBody from "$lib/components/common/ModalBody.svelte";
|
import ModalBody from "$lib/components/common/ModalBody.svelte";
|
||||||
import { getItemListContext } from "$lib/components/contexts/ItemListContext.svelte";
|
import { getItemListContext } from "$lib/components/contexts/ItemListContext.svelte";
|
||||||
|
import { getItemMultiListContext } from "$lib/components/contexts/ItemMultiListContext.svelte";
|
||||||
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
|
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
|
||||||
import { getProjectContext } from "$lib/components/contexts/ProjectContext.svelte";
|
import { getProjectContext } from "$lib/components/contexts/ProjectContext.svelte";
|
||||||
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
||||||
import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte";
|
import { getScopeListContext } from "$lib/components/contexts/ScopeListContext.svelte";
|
||||||
|
import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte";
|
||||||
import AcquiredTimeInput from "$lib/components/controls/AcquiredTimeInput.svelte";
|
import AcquiredTimeInput from "$lib/components/controls/AcquiredTimeInput.svelte";
|
||||||
|
import RequirementSelect from "$lib/components/controls/RequirementSelect.svelte";
|
||||||
import StatInput from "$lib/components/controls/StatInput.svelte";
|
import StatInput from "$lib/components/controls/StatInput.svelte";
|
||||||
import type Item from "$lib/models/item";
|
import type Item from "$lib/models/item";
|
||||||
import type { ItemInput } from "$lib/models/item";
|
import type { ItemInput } from "$lib/models/item";
|
||||||
@@ -19,16 +22,20 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
|
|
||||||
const {currentModal, closeModal} = getModalContext();
|
const {currentModal, closeModal} = getModalContext();
|
||||||
const {scope} = getScopeContext();
|
const {scope} = getScopeContext();
|
||||||
|
const {scopes} = getScopeListContext();
|
||||||
const {project, reloadProject} = getProjectContext();
|
const {project, reloadProject} = getProjectContext();
|
||||||
const {reloadItemList} = getItemListContext();
|
const {reloadItemList} = getItemListContext();
|
||||||
const {reloadSprintList} = getSprintListContext();
|
const {reloadSprintList} = getSprintListContext();
|
||||||
|
const {reloadItemLists} = getItemMultiListContext();
|
||||||
|
|
||||||
let item: ItemInput
|
let item: ItemInput
|
||||||
let itemId: number
|
let itemId: number
|
||||||
|
let scopeId: number
|
||||||
let currentStats: ItemInput["stats"]
|
let currentStats: ItemInput["stats"]
|
||||||
let openedDate: Date
|
let openedDate: Date
|
||||||
let requirementName: string
|
let requirementName: string
|
||||||
let op: string
|
let op: string
|
||||||
|
let initOnList: number[] = [];
|
||||||
|
|
||||||
let error: string
|
let error: string
|
||||||
let loading: boolean
|
let loading: boolean
|
||||||
@@ -38,9 +45,11 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
case "item.create":
|
case "item.create":
|
||||||
initCreate($scope, $currentModal.requirement)
|
initCreate($scope, $currentModal.requirement)
|
||||||
break;
|
break;
|
||||||
case "item.edit":
|
case "item.edit": {
|
||||||
initEdit($currentModal.item)
|
const reqId = $currentModal.item.requirementId;
|
||||||
|
initEdit($currentModal.item, $project.requirements.find(r => r.id === reqId), $scope)
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
loading = false;
|
loading = false;
|
||||||
@@ -52,6 +61,8 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
let stats = requirement?.stats.map(s => ({statId: s.id, required: 0, acquired: 0}));
|
let stats = requirement?.stats.map(s => ({statId: s.id, required: 0, acquired: 0}));
|
||||||
if (stats == null) {
|
if (stats == null) {
|
||||||
stats = scope.stats.map(s => ({statId: s.id, required: 0, acquired: 0}))
|
stats = scope.stats.map(s => ({statId: s.id, required: 0, acquired: 0}))
|
||||||
|
} else {
|
||||||
|
initOnList = requirement.stats.filter(s => s.required > 0).map(s => s.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
item = {
|
item = {
|
||||||
@@ -64,28 +75,40 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
op = "Create"
|
op = "Create"
|
||||||
|
scopeId = scope.id;
|
||||||
openedDate = new Date();
|
openedDate = new Date();
|
||||||
requirementName = requirement?.name;
|
requirementName = requirement?.name;
|
||||||
show = true;
|
show = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initEdit(current: Item) {
|
function initEdit(current: Item, requirement?: Requirement, scope?: Scope) {
|
||||||
const req = $project.requirements?.find(r => r.id === current.requirementId);
|
const req = $project.requirements?.find(r => r.id === current.requirementId);
|
||||||
|
let ctxStats = requirement?.stats.map(s => ({statId: s.id, required: 0, acquired: 0}));
|
||||||
|
if (ctxStats == null || ctxStats.length === 0) {
|
||||||
|
let actualScope = scope;
|
||||||
|
if (actualScope.id !== current.scopeId && $scopes?.length > 0) {
|
||||||
|
actualScope = $scopes.find(s => s.id === current.scopeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxStats = actualScope.stats.map(s => ({statId: s.id, required: 0, acquired: 0}));
|
||||||
|
}
|
||||||
|
const inputStats = current.stats.map(s => ({statId: s.id, acquired: s.acquired, required: s.required}));
|
||||||
|
|
||||||
item = {
|
item = {
|
||||||
name: current.name,
|
name: current.name,
|
||||||
description: current.description,
|
description: current.description,
|
||||||
requirementId: current.requirementId,
|
requirementId: current.requirementId,
|
||||||
stats: current.stats.map(s => ({statId: s.id, acquired: s.acquired, required: s.required})),
|
stats: ctxStats.map(s => inputStats.find(s2 => s2.statId === s.statId) || s),
|
||||||
acquiredTime: formatFormTime(current.acquiredTime),
|
acquiredTime: formatFormTime(current.acquiredTime),
|
||||||
scheduledDate: current.scheduledDate,
|
scheduledDate: current.scheduledDate,
|
||||||
};
|
};
|
||||||
itemId = current.id;
|
itemId = current.id;
|
||||||
|
scopeId = current.scopeId;
|
||||||
currentStats = [...item.stats];
|
currentStats = [...item.stats];
|
||||||
|
|
||||||
op = "Edit"
|
op = "Edit"
|
||||||
openedDate = new Date();
|
openedDate = new Date();
|
||||||
requirementName = req?.name;
|
requirementName = current.requirement?.name;
|
||||||
show = true;
|
show = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +135,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await sl3(fetch).createItem($scope.id, submission);
|
await sl3(fetch).createItem(scopeId, submission);
|
||||||
break;
|
break;
|
||||||
case "Edit":
|
case "Edit":
|
||||||
if (!submission.acquiredTime) {
|
if (!submission.acquiredTime) {
|
||||||
@@ -123,7 +146,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
}
|
}
|
||||||
|
|
||||||
submission.stats = statDiff(currentStats, submission.stats)
|
submission.stats = statDiff(currentStats, submission.stats)
|
||||||
await sl3(fetch).updateItem($scope.id, itemId, submission);
|
await sl3(fetch).updateItem(scopeId, itemId, submission);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +157,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
|
|
||||||
await reloadItemList();
|
await reloadItemList();
|
||||||
await reloadSprintList();
|
await reloadSprintList();
|
||||||
|
reloadItemLists();
|
||||||
|
|
||||||
closeModal();
|
closeModal();
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
@@ -162,10 +186,21 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext
|
|||||||
<ModalBody>
|
<ModalBody>
|
||||||
{#if requirementName != null}
|
{#if requirementName != null}
|
||||||
<label for="req">Project Requirement</label>
|
<label for="req">Project Requirement</label>
|
||||||
<input disabled name="req" type="text" value={requirementName} />
|
{#if $project.id != null}
|
||||||
|
<RequirementSelect disabled={op === "Create"} requirements={$project.requirements} bind:value={item.requirementId} />
|
||||||
|
{:else}
|
||||||
|
<input disabled name="req" type="text" value={requirementName} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<label for="stats">Stats</label>
|
<label for="stats">Stats</label>
|
||||||
<StatInput showAcquired={op === "Edit" && !!item.acquiredTime} showRequired hideUnseen={!!item.requirementId} bind:value={item.stats} />
|
<StatInput
|
||||||
|
zeroDeletes showRequired
|
||||||
|
overrideScopeId={scopeId}
|
||||||
|
initOnList={initOnList}
|
||||||
|
showAcquired={op === "Edit" && !!item.acquiredTime}
|
||||||
|
hideUnseen={!!item.requirementId}
|
||||||
|
bind:value={item.stats}
|
||||||
|
/>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
</form>
|
</form>
|
||||||
@@ -21,6 +21,7 @@ import { statDiff } from "$lib/utils/stat";
|
|||||||
let projectId: number
|
let projectId: number
|
||||||
let requirementId: number
|
let requirementId: number
|
||||||
let oldStats: RequirementInput["stats"]
|
let oldStats: RequirementInput["stats"]
|
||||||
|
let showAggregateRequired: boolean
|
||||||
|
|
||||||
let error: string
|
let error: string
|
||||||
let loading: boolean
|
let loading: boolean
|
||||||
@@ -44,6 +45,8 @@ import { statDiff } from "$lib/utils/stat";
|
|||||||
show = false;
|
show = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: showAggregateRequired = requirement?.stats?.find(s => s.required > 0) == null;
|
||||||
|
|
||||||
function initCreate(project: ProjectEntry) {
|
function initCreate(project: ProjectEntry) {
|
||||||
requirement = {
|
requirement = {
|
||||||
name: "",
|
name: "",
|
||||||
@@ -51,6 +54,7 @@ import { statDiff } from "$lib/utils/stat";
|
|||||||
stats: [],
|
stats: [],
|
||||||
status: Status.Available,
|
status: Status.Available,
|
||||||
isCoarse: false,
|
isCoarse: false,
|
||||||
|
aggregateRequired: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
projectId = project.id;
|
projectId = project.id;
|
||||||
@@ -64,6 +68,7 @@ import { statDiff } from "$lib/utils/stat";
|
|||||||
status: current.status,
|
status: current.status,
|
||||||
stats: current.stats.map(s => ({acquired: s.acquired, required: s.required, statId: s.id})),
|
stats: current.stats.map(s => ({acquired: s.acquired, required: s.required, statId: s.id})),
|
||||||
isCoarse: current.isCoarse,
|
isCoarse: current.isCoarse,
|
||||||
|
aggregateRequired: current.aggregateRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
oldStats = [...requirement.stats];
|
oldStats = [...requirement.stats];
|
||||||
@@ -76,6 +81,10 @@ import { statDiff } from "$lib/utils/stat";
|
|||||||
error = null;
|
error = null;
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
|
if (requirement.stats.find(s => s.required > 0)) {
|
||||||
|
requirement.aggregateRequired = 0;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case "Create":
|
case "Create":
|
||||||
@@ -115,6 +124,10 @@ import { statDiff } from "$lib/utils/stat";
|
|||||||
<input name="name" type="text" bind:value={requirement.name} />
|
<input name="name" type="text" bind:value={requirement.name} />
|
||||||
<label for="description">Description</label>
|
<label for="description">Description</label>
|
||||||
<textarea name="description" bind:value={requirement.description} />
|
<textarea name="description" bind:value={requirement.description} />
|
||||||
|
{#if showAggregateRequired}
|
||||||
|
<label for="aggregateRequired">Aggregate Required (Zero for old behavior)</label>
|
||||||
|
<input name="aggregateRequired" type="number" bind:value={requirement.aggregateRequired} />
|
||||||
|
{/if}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<label for="stats">Status</label>
|
<label for="stats">Status</label>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { partsDiff } from "$lib/utils/sprint";
|
|||||||
|
|
||||||
let sprint: SprintInput
|
let sprint: SprintInput
|
||||||
let sprintId: number
|
let sprintId: number
|
||||||
|
let scopeId: number
|
||||||
let oldParts: SprintInputPart[]
|
let oldParts: SprintInputPart[]
|
||||||
let intervalName: string;
|
let intervalName: string;
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ import { partsDiff } from "$lib/utils/sprint";
|
|||||||
parts: [],
|
parts: [],
|
||||||
}
|
}
|
||||||
intervalName = "this_month";
|
intervalName = "this_month";
|
||||||
|
scopeId = scope.id;
|
||||||
|
|
||||||
op = "Create"
|
op = "Create"
|
||||||
openedDate = new Date();
|
openedDate = new Date();
|
||||||
@@ -80,6 +82,7 @@ import { partsDiff } from "$lib/utils/sprint";
|
|||||||
isUnweighted: current.isUnweighted,
|
isUnweighted: current.isUnweighted,
|
||||||
};
|
};
|
||||||
sprintId = current.id;
|
sprintId = current.id;
|
||||||
|
scopeId = current.scopeId;
|
||||||
intervalName = "specific_dates";
|
intervalName = "specific_dates";
|
||||||
|
|
||||||
if (sprint.kind === SprintKind.Stats) {
|
if (sprint.kind === SprintKind.Stats) {
|
||||||
@@ -107,16 +110,16 @@ import { partsDiff } from "$lib/utils/sprint";
|
|||||||
try {
|
try {
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case "Create":
|
case "Create":
|
||||||
await sl3(fetch).createSprint($scope.id, submission);
|
await sl3(fetch).createSprint(scopeId, submission);
|
||||||
break;
|
break;
|
||||||
case "Edit":
|
case "Edit":
|
||||||
await sl3(fetch).updateSprint($scope.id, sprintId, submission);
|
await sl3(fetch).updateSprint(scopeId, sprintId, submission);
|
||||||
const {added, removed} = partsDiff(oldParts, submission.parts)
|
const {added, removed} = partsDiff(oldParts, submission.parts)
|
||||||
for (const part of added) {
|
for (const part of added) {
|
||||||
await sl3(fetch).upsertSprintPart($scope.id, sprintId, part).catch(() => {});
|
await sl3(fetch).upsertSprintPart(scopeId, sprintId, part).catch(() => {});
|
||||||
}
|
}
|
||||||
for (const part of removed) {
|
for (const part of removed) {
|
||||||
await sl3(fetch).deleteSprintPart($scope.id, sprintId, part).catch(() => {});
|
await sl3(fetch).deleteSprintPart(scopeId, sprintId, part).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -153,15 +156,15 @@ import { partsDiff } from "$lib/utils/sprint";
|
|||||||
<label for="aggregateValue">Aggregate Goal</label>
|
<label for="aggregateValue">Aggregate Goal</label>
|
||||||
<input name="aggregateValue" type="number" bind:value={sprint.aggregateRequired} />
|
<input name="aggregateValue" type="number" bind:value={sprint.aggregateRequired} />
|
||||||
{/if}
|
{/if}
|
||||||
|
<Checkbox bind:checked={sprint.isTimed} label="Sprint is timed" />
|
||||||
|
<Checkbox bind:checked={sprint.isCoarse} label="Show only aggregate" />
|
||||||
|
<Checkbox bind:checked={sprint.isUnweighted} label="Aggregate is unweighted" />
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<label for="kind">Kind</label>
|
<label for="kind">Kind</label>
|
||||||
<SprintKindSelect disabled={op === "Edit"} bind:kind={sprint.kind} />
|
<SprintKindSelect disabled={op === "Edit"} bind:kind={sprint.kind} />
|
||||||
<label for="parts">Parts</label>
|
<label for="parts">Parts</label>
|
||||||
<PartInput bind:value={sprint.parts} kind={sprint.kind} />
|
<PartInput bind:value={sprint.parts} kind={sprint.kind} scopeId={scopeId} />
|
||||||
<Checkbox bind:checked={sprint.isTimed} label="Sprint is timed" />
|
|
||||||
<Checkbox bind:checked={sprint.isCoarse} label="Show only aggregate" />
|
|
||||||
<Checkbox bind:checked={sprint.isUnweighted} label="Aggregate is unweighted" />
|
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
</form>
|
</form>
|
||||||
@@ -103,7 +103,7 @@ import Checkbox from "$lib/components/layout/Checkbox.svelte";
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form on:submit|preventDefault={submit}>
|
<form on:submit|preventDefault={submit}>
|
||||||
<Modal closable show={show} verb={op} noun="Item" disabled={loading} error={error}>
|
<Modal closable show={show} verb={op} noun="Stat" disabled={loading} error={error}>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input name="name" type="text" bind:value={stat.name} />
|
<input name="name" type="text" bind:value={stat.name} />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { TimeInterval } from "./common";
|
import type { TimeInterval } from "./common";
|
||||||
import type { StatValueInput, StatProgress } from "./stat";
|
import type { StatValueInput, StatProgress } from "./stat";
|
||||||
|
import type Status from "./status";
|
||||||
|
|
||||||
export default interface Item {
|
export default interface Item {
|
||||||
id: number
|
id: number
|
||||||
@@ -13,6 +14,25 @@ export default interface Item {
|
|||||||
acquiredTime?: string
|
acquiredTime?: string
|
||||||
scheduledDate?: string
|
scheduledDate?: string
|
||||||
stats: StatProgress[]
|
stats: StatProgress[]
|
||||||
|
weightedAcquired: number
|
||||||
|
weightedRequired: number
|
||||||
|
project: ItemProject
|
||||||
|
requirement: ItemRequirement
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ItemProject {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
status: Status
|
||||||
|
statusName: string
|
||||||
|
createdTime: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ItemRequirement {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
status: Status
|
||||||
|
statusName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemInput {
|
export interface ItemInput {
|
||||||
@@ -36,6 +56,8 @@ export interface ItemFilter {
|
|||||||
createdTime?: TimeInterval<Date | string>
|
createdTime?: TimeInterval<Date | string>
|
||||||
acquiredTime?: TimeInterval<Date | string>
|
acquiredTime?: TimeInterval<Date | string>
|
||||||
loose?: boolean
|
loose?: boolean
|
||||||
|
unAcquired?: boolean
|
||||||
|
unScheduled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemGroup {
|
export interface ItemGroup {
|
||||||
@@ -46,9 +68,17 @@ export interface ItemGroup {
|
|||||||
export interface ItemGroupReference {
|
export interface ItemGroupReference {
|
||||||
idx: number
|
idx: number
|
||||||
event: "created" | "acquired" | "scheduled"
|
event: "created" | "acquired" | "scheduled"
|
||||||
|
time: string
|
||||||
|
|
||||||
sortKey?: string
|
sortKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GroupItemsOptions {
|
||||||
|
hideCreated?: boolean
|
||||||
|
hideAcquired?: boolean
|
||||||
|
hideScheduled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
// this_week
|
// this_week
|
||||||
// this_month
|
// this_month
|
||||||
// all_time
|
// all_time
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export interface Requirement {
|
|||||||
totalRequired: number
|
totalRequired: number
|
||||||
totalPlanned: number
|
totalPlanned: number
|
||||||
isCoarse: boolean
|
isCoarse: boolean
|
||||||
|
aggregateRequired: number
|
||||||
stats: StatProgressWithPlanned[]
|
stats: StatProgressWithPlanned[]
|
||||||
items: Item[]
|
items: Item[]
|
||||||
}
|
}
|
||||||
@@ -47,5 +48,6 @@ export interface RequirementInput {
|
|||||||
description: string
|
description: string
|
||||||
status: Status
|
status: Status
|
||||||
isCoarse: boolean
|
isCoarse: boolean
|
||||||
|
aggregateRequired: number
|
||||||
stats: StatValueInput[]
|
stats: StatValueInput[]
|
||||||
}
|
}
|
||||||
@@ -8,4 +8,14 @@ enum Status {
|
|||||||
Dropped,
|
Dropped,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const STATUS_NAMES: Record<Status, string> = {
|
||||||
|
[Status.Blocked]: "Blocked",
|
||||||
|
[Status.Available]: "Available",
|
||||||
|
[Status.Background]: "Background",
|
||||||
|
[Status.Active]: "Active",
|
||||||
|
[Status.Completed]: "Completed",
|
||||||
|
[Status.Failed]: "Failed",
|
||||||
|
[Status.Dropped]: "Dropped",
|
||||||
|
}
|
||||||
|
|
||||||
export default Status;
|
export default Status;
|
||||||
@@ -114,7 +114,7 @@ export function endOfDay(now: Date): Date {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function monthName(date: Date): string {
|
export function monthName(date: Date): string {
|
||||||
return monthNames[date.getMonth()]
|
return monthNames[date.getMonth() - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addDays(date: Date, days: number): Date {
|
export function addDays(date: Date, days: number): Date {
|
||||||
@@ -133,4 +133,24 @@ export function addYears(date: Date, years: number): Date {
|
|||||||
const newDate = new Date(date.getTime());
|
const newDate = new Date(date.getTime());
|
||||||
newDate.setFullYear(newDate.getFullYear() + years);
|
newDate.setFullYear(newDate.getFullYear() + years);
|
||||||
return newDate;
|
return newDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatPrettyTime(date: Date, now: Date) {
|
||||||
|
const d = startOfDay(date);
|
||||||
|
const n = startOfDay(now);
|
||||||
|
|
||||||
|
const diff = (d.getTime() - n.getTime()) / (86400000);
|
||||||
|
|
||||||
|
const hhmm = `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||||
|
if (diff === 0) {
|
||||||
|
return `today at ${hhmm}`;
|
||||||
|
} else if (diff === 1) {
|
||||||
|
return `tomorrow at ${hhmm}`;
|
||||||
|
} else if (diff === -1) {
|
||||||
|
return `yesterday at ${hhmm}`;
|
||||||
|
} else if (diff > -3 && diff < 6) {
|
||||||
|
return `${weekDay[date.getDay()]} at ${hhmm}`;
|
||||||
|
} else {
|
||||||
|
return `${monthName(date)} ${date.getDate()} at ${hhmm}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import type { ItemGroup, ItemGroupReference } from "$lib/models/item";
|
|
||||||
import type Item from "$lib/models/item";
|
|
||||||
import { formatDate, formatDateTime, formatTime, formatWeekdayDate } from "./date";
|
|
||||||
|
|
||||||
export function groupItems(items: Item[], minDate?: string): ItemGroup[] {
|
|
||||||
let groups: Record<string, ItemGroup> = {};
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; ++i) {
|
|
||||||
const item = items[i];
|
|
||||||
const createKey = formatDate(item.createdTime)
|
|
||||||
const createSortKey = `${createKey} ${formatTime(item.createdTime)}`
|
|
||||||
|
|
||||||
if (!item.acquiredTime || item.acquiredTime >= item.createdTime) {
|
|
||||||
addItem(groups, createKey, i, createSortKey, item, "created");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.scheduledDate) {
|
|
||||||
addItem(groups, item.scheduledDate, i, `${item.scheduledDate} 23:59:59`, item, "scheduled");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.acquiredTime) {
|
|
||||||
addItem(groups, formatDate(item.acquiredTime), i, formatDateTime(item.acquiredTime), item, "acquired");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(groups).sort((a,b) => (
|
|
||||||
b.localeCompare(a)
|
|
||||||
)).map(k => ({
|
|
||||||
...groups[k],
|
|
||||||
list: groups[k].list.sort((a, b) => {
|
|
||||||
return b.sortKey.localeCompare(a.sortKey);
|
|
||||||
}).map(r => ({...r, sortKey: void(0)})),
|
|
||||||
})).filter(g => minDate == null || g.label > minDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addItem(groups: Record<string, ItemGroup>, key: string, index: number, sortKey: string, item: Item, event: ItemGroupReference["event"]) {
|
|
||||||
if (groups[key] == null) {
|
|
||||||
groups[key] = {
|
|
||||||
label: formatWeekdayDate(sortKey),
|
|
||||||
list: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const i = groups[key].list.findIndex(r => r.idx === index)
|
|
||||||
if (i == -1) {
|
|
||||||
groups[key].list.push({
|
|
||||||
idx: index,
|
|
||||||
event: event,
|
|
||||||
sortKey: sortKey,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
groups[key].list[i].event = event;
|
|
||||||
groups[key].list[i].sortKey = sortKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export default function shortestDecimal(n: number) {
|
||||||
|
let s = n.toFixed(3);
|
||||||
|
while (s.endsWith("0")) {
|
||||||
|
s = s.slice(0, -1)
|
||||||
|
}
|
||||||
|
if (s.endsWith(".")) {
|
||||||
|
s = s.slice(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function significantDecimals(n: number) {
|
||||||
|
let s = shortestDecimal(n);
|
||||||
|
let d = s.indexOf('.');
|
||||||
|
if (d == -1) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
if (d >= 4) {
|
||||||
|
return s.slice(0, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.slice(0, 5);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
export default function deepEqual(a: Object, b: Object) {
|
||||||
|
if (a === void(0)) {
|
||||||
|
if (b !== void(0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a === null) {
|
||||||
|
if (b !== null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a instanceof Date) {
|
||||||
|
if (b instanceof Date) {
|
||||||
|
b = b.toISOString();
|
||||||
|
}
|
||||||
|
a = a.toISOString();
|
||||||
|
|
||||||
|
return a === b;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(a)) {
|
||||||
|
if (!Array.isArray(b) || b.length !== a.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (!deepEqual(a[i], b[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof(a) === "object") {
|
||||||
|
if (typeof(b) !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in a) {
|
||||||
|
if (!a.hasOwnProperty(key)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!deepEqual(a[key], b[key])) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a === b;
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ export function statDiff(before: StatValueInput[], after: StatValueInput[], dele
|
|||||||
|
|
||||||
for (const s of after) {
|
for (const s of after) {
|
||||||
const old = before.find(s2 => s2.statId === s.statId);
|
const old = before.find(s2 => s2.statId === s.statId);
|
||||||
if (old == null || old.required != s.required || old.acquired != s.required) {
|
if (old == null || old.required != s.required || old.acquired != s.acquired) {
|
||||||
res.push(s);
|
res.push(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { TimeInterval } from "../models/common";
|
|||||||
import { addDays, addMonths, addYears, formatDate, startOfDay, startOfMonth, startOfWeek, startOfYear } from "./date";
|
import { addDays, addMonths, addYears, formatDate, startOfDay, startOfMonth, startOfWeek, startOfYear } from "./date";
|
||||||
|
|
||||||
export default function parseInterval(s: string, date: Date): TimeInterval<Date> {
|
export default function parseInterval(s: string, date: Date): TimeInterval<Date> {
|
||||||
const [verb, args] = s.split(":")
|
const [verb, args] = s.split(";")[0].split(":")
|
||||||
|
|
||||||
switch (verb) {
|
switch (verb) {
|
||||||
case "":
|
case "":
|
||||||
@@ -25,9 +25,9 @@ export default function parseInterval(s: string, date: Date): TimeInterval<Date>
|
|||||||
const [amount, unit] = unitNumber(args);
|
const [amount, unit] = unitNumber(args);
|
||||||
switch (unit) {
|
switch (unit) {
|
||||||
case "d": case "days":
|
case "d": case "days":
|
||||||
return {display: "days", min: startOfDay(addDays(date, -amount)), max: startOfDay(addDays(date, 1))}
|
return {display: "days", min: startOfDay(addDays(date, -(amount - 1))), max: startOfDay(addDays(date, 1))}
|
||||||
case "w": case "weeks":
|
case "w": case "weeks":
|
||||||
return {display: "weeks", min: startOfDay(addDays(date, -amount * 7)), max: startOfDay(addDays(date, 1))}
|
return {display: "weeks", min: startOfDay(addDays(date, -((amount * 7) - 1))), max: startOfDay(addDays(date, 1))}
|
||||||
case "m": case "months":
|
case "m": case "months":
|
||||||
return {display: "days", min: startOfDay(addMonths(date, -amount)), max: startOfDay(addDays(date, 1))}
|
return {display: "days", min: startOfDay(addMonths(date, -amount)), max: startOfDay(addDays(date, 1))}
|
||||||
case "y": case "years":
|
case "y": case "years":
|
||||||
@@ -55,6 +55,19 @@ export default function parseInterval(s: string, date: Date): TimeInterval<Date>
|
|||||||
case "last_year":
|
case "last_year":
|
||||||
return {display: "year", min: startOfYear(addYears(date, -1)), max: startOfYear(date)}
|
return {display: "year", min: startOfYear(addYears(date, -1)), max: startOfYear(date)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function morningInterval(interval: TimeInterval<Date>) {
|
||||||
|
if (!interval) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
min: new Date(interval.min.getTime() + 3600000 * 5),
|
||||||
|
max: new Date(interval.max.getTime() + 3600000 * 5),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function intervalLabel(s: string): string {
|
export function intervalLabel(s: string): string {
|
||||||
|
|||||||
@@ -26,12 +26,12 @@
|
|||||||
import ProjectListContext from "$lib/components/contexts/ProjectListContext.svelte";
|
import ProjectListContext from "$lib/components/contexts/ProjectListContext.svelte";
|
||||||
import ScopeMenu from "$lib/components/scope/ScopeMenu.svelte";
|
import ScopeMenu from "$lib/components/scope/ScopeMenu.svelte";
|
||||||
import { scopePrettyId } from "$lib/utils/prettyIds";
|
import { scopePrettyId } from "$lib/utils/prettyIds";
|
||||||
import ScopeHeader from "$lib/components/scope/ScopeHeader.svelte";
|
import ScopeHeader from "$lib/components/scope/ScopeHeader.svelte";
|
||||||
|
|
||||||
export let scope: Scope;
|
export let scope: Scope;
|
||||||
export let projects: ProjectEntry[];
|
export let projects: ProjectEntry[];
|
||||||
|
|
||||||
let hideMobile;
|
let hideMobile: boolean;
|
||||||
$: hideMobile = $page.url.pathname !== `/${scopePrettyId(scope)}`
|
$: hideMobile = $page.url.pathname !== `/${scopePrettyId(scope)}`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
import type { Load } from "@sveltejs/kit/types/internal";
|
import type { Load } from "@sveltejs/kit/types/internal";
|
||||||
|
|
||||||
import { sl3 } from "$lib/clients/sl3";
|
import { sl3 } from "$lib/clients/sl3";
|
||||||
import parseInterval, { datesOf, intervalLabel } from "$lib/utils/timeinterval";
|
import parseInterval, { datesOf } from "$lib/utils/timeinterval";
|
||||||
import { groupItems } from "$lib/utils/items";
|
|
||||||
import type Item from "$lib/models/item";
|
import type Item from "$lib/models/item";
|
||||||
import type { ItemFilter, ItemGroup } from "$lib/models/item";
|
import type { ItemFilter, ItemGroup } from "$lib/models/item";
|
||||||
|
|
||||||
export const load: Load = async({ fetch, params }) => {
|
export const load: Load = async({ fetch, params, session }) => {
|
||||||
const scopeId = parseInt(params.scope.split("-")[0]);
|
const scopeId = parseInt(params.scope.split("-")[0]);
|
||||||
|
|
||||||
const interval = parseInterval(params.interval, new Date());
|
const interval = parseInterval(params.interval, new Date());
|
||||||
@@ -16,28 +15,30 @@
|
|||||||
acquiredTime: interval,
|
acquiredTime: interval,
|
||||||
scheduledDate: datesOf(interval),
|
scheduledDate: datesOf(interval),
|
||||||
};
|
};
|
||||||
const items = await sl3(fetch).listItems(scopeId, filter);
|
const {items, groups} = await sl3(fetch).listItems(scopeId, filter, true, session.groupOptions);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
items, filter,
|
items, groups, filter,
|
||||||
intervalString: params.interval,
|
intervalString: params.interval,
|
||||||
groups: groupItems(items, datesOf(interval)?.min),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { goto } from "$app/navigation";
|
||||||
|
import { session } from "$app/stores";
|
||||||
|
import { browser } from "$app/env";
|
||||||
import Main from "$lib/components/layout/Main.svelte";
|
import Main from "$lib/components/layout/Main.svelte";
|
||||||
|
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
||||||
import DeletionModal from "$lib/modals/DeletionModal.svelte";
|
import DeletionModal from "$lib/modals/DeletionModal.svelte";
|
||||||
import ItemAcquireModal from "$lib/modals/ItemAcquireModal.svelte";
|
import ItemAcquireModal from "$lib/modals/ItemAcquireModal.svelte";
|
||||||
import ItemCreateModal from "$lib/modals/ItemCreateModal.svelte";
|
import ItemCreateModal from "$lib/modals/ItemCreateModal.svelte";
|
||||||
import { goto } from "$app/navigation";
|
|
||||||
import HistorySelector from "$lib/components/common/HistorySelector.svelte";
|
import HistorySelector from "$lib/components/common/HistorySelector.svelte";
|
||||||
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
|
||||||
import ItemListContext from "$lib/components/contexts/ItemListContext.svelte";
|
import ItemListContext from "$lib/components/contexts/ItemListContext.svelte";
|
||||||
import HistoryGroupList from "$lib/components/history/HistoryGroupList.svelte";
|
import HistoryGroupList from "$lib/components/history/HistoryGroupList.svelte";
|
||||||
|
import ToggleIcon from "$lib/components/history/ToggleIcon.svelte";
|
||||||
|
|
||||||
export let intervalString: string
|
export let intervalString: string
|
||||||
export let items: Item[]
|
export let items: Item[]
|
||||||
@@ -51,14 +52,39 @@
|
|||||||
|
|
||||||
lastHistoryRange.set(target.value);
|
lastHistoryRange.set(target.value);
|
||||||
|
|
||||||
goto(`./${target.value}`);
|
goto(`./${target.value}`, {replaceState: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerReload() {
|
||||||
|
goto(`./${intervalString}`, {replaceState: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (browser) {
|
||||||
|
if ($session.groupOptions == null) {
|
||||||
|
try {
|
||||||
|
$session.groupOptions = JSON.parse(localStorage.getItem("sl3.history.group_options")||"{}");
|
||||||
|
triggerReload();
|
||||||
|
} catch(_) {
|
||||||
|
$session.groupOptions = {};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
localStorage.setItem("sl3.history.group_options", JSON.stringify($session.groupOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ItemListContext filter={filter} groups={groups} items={items}>
|
<ItemListContext filter={filter} groups={groups} items={items}>
|
||||||
<Main big>
|
<Main big>
|
||||||
<HistorySelector value={intervalString} on:change={changeInterval} />
|
<HistorySelector value={intervalString} on:change={changeInterval} >
|
||||||
<HistoryGroupList />
|
{#if $session.groupOptions != null}
|
||||||
|
<ToggleIcon inverse name="lightbulb" bind:value={$session.groupOptions.hideCreated} on:click={triggerReload} />
|
||||||
|
<ToggleIcon inverse name="check_slot" bind:value={$session.groupOptions.hideAcquired} on:click={triggerReload} />
|
||||||
|
<ToggleIcon inverse name="calendar" bind:value={$session.groupOptions.hideScheduled} on:click={triggerReload} />
|
||||||
|
{/if}
|
||||||
|
</HistorySelector>
|
||||||
|
<HistoryGroupList intervalString={intervalString} />
|
||||||
</Main>
|
</Main>
|
||||||
|
|
||||||
<ItemCreateModal />
|
<ItemCreateModal />
|
||||||
|
|||||||
@@ -1,29 +1,41 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
import type { Load } from "@sveltejs/kit/types/internal";
|
import type { Load } from "@sveltejs/kit/types/internal";
|
||||||
|
|
||||||
|
function generateItemFilters(now: Date): {scheduledFilter: ItemFilter, acquiredFilter: ItemFilter, looseFilter: ItemFilter} {
|
||||||
|
return {
|
||||||
|
scheduledFilter: {
|
||||||
|
scheduledDate: datesOf(parseInterval("next:7d", now)),
|
||||||
|
},
|
||||||
|
acquiredFilter: {
|
||||||
|
acquiredTime: morningInterval(parseInterval("today", now)),
|
||||||
|
},
|
||||||
|
looseFilter: {
|
||||||
|
loose: true,
|
||||||
|
unAcquired: true,
|
||||||
|
unScheduled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const load: Load = async({params, url, fetch}) => {
|
export const load: Load = async({params, url, fetch}) => {
|
||||||
const scopeId = parseInt(params.scope.split("-")[0]);
|
const scopeId = parseInt(params.scope.split("-")[0]);
|
||||||
|
|
||||||
console.log(
|
const {acquiredFilter, looseFilter, scheduledFilter} = generateItemFilters(new Date());
|
||||||
datesOf(parseInterval("next:7d", new Date)),
|
|
||||||
parseInterval("today", new Date),
|
|
||||||
)
|
|
||||||
|
|
||||||
const scheduledItems = (await sl3(fetch).listItems(scopeId, {
|
const scheduledItems = (await sl3(fetch).listItems(scopeId, scheduledFilter));
|
||||||
scheduledDate: datesOf(parseInterval("next:7d", new Date)),
|
const acquiredItems = (await sl3(fetch).listItems(scopeId, acquiredFilter));
|
||||||
}))
|
const looseItems = (await sl3(fetch).listItems(scopeId, looseFilter));
|
||||||
const acquiredItems = (await sl3(fetch).listItems(scopeId, {
|
|
||||||
acquiredTime: parseInterval("today", new Date),
|
|
||||||
}))
|
|
||||||
const sprints = await sl3(fetch).listSprints(scopeId);
|
const sprints = await sl3(fetch).listSprints(scopeId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {scheduledItems, acquiredItems, sprints}
|
props: {scheduledItems, acquiredItems, looseItems, sprints}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { sl3 } from "$lib/clients/sl3";
|
||||||
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte";
|
||||||
import Column from "$lib/components/layout/Column.svelte";
|
import Column from "$lib/components/layout/Column.svelte";
|
||||||
import Columns from "$lib/components/layout/Columns.svelte";
|
import Columns from "$lib/components/layout/Columns.svelte";
|
||||||
@@ -33,10 +45,8 @@
|
|||||||
import StatSubSection from "$lib/components/scope/StatSubSection.svelte";
|
import StatSubSection from "$lib/components/scope/StatSubSection.svelte";
|
||||||
import DeletionModal from "$lib/modals/DeletionModal.svelte";
|
import DeletionModal from "$lib/modals/DeletionModal.svelte";
|
||||||
import StatCreateEditModal from "$lib/modals/StatCreateEditModal.svelte";
|
import StatCreateEditModal from "$lib/modals/StatCreateEditModal.svelte";
|
||||||
import { sl3 } from "$lib/clients/sl3";
|
import parseInterval, { datesOf, morningInterval } from "$lib/utils/timeinterval";
|
||||||
import parseInterval, { datesOf } from "$lib/utils/timeinterval";
|
|
||||||
import type Item from "$lib/models/item";
|
import type Item from "$lib/models/item";
|
||||||
import ItemSubSection from "$lib/components/project/ItemSubSection.svelte";
|
|
||||||
import ItemCreateModal from "$lib/modals/ItemCreateModal.svelte";
|
import ItemCreateModal from "$lib/modals/ItemCreateModal.svelte";
|
||||||
import Card from "$lib/components/common/Card.svelte";
|
import Card from "$lib/components/common/Card.svelte";
|
||||||
import CardHeader from "$lib/components/common/CardHeader.svelte";
|
import CardHeader from "$lib/components/common/CardHeader.svelte";
|
||||||
@@ -48,13 +58,20 @@
|
|||||||
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
|
import { getModalContext } from "$lib/components/contexts/ModalContext.svelte";
|
||||||
import ProjectCreateEditModal from "$lib/modals/ProjectCreateEditModal.svelte";
|
import ProjectCreateEditModal from "$lib/modals/ProjectCreateEditModal.svelte";
|
||||||
import ScopeCreateUpdateModal from "$lib/modals/ScopeCreateUpdateModal.svelte";
|
import ScopeCreateUpdateModal from "$lib/modals/ScopeCreateUpdateModal.svelte";
|
||||||
|
import type { ItemFilter } from "$lib/models/item";
|
||||||
|
import ItemMultiListContext from "$lib/components/contexts/ItemMultiListContext.svelte";
|
||||||
|
import { getTimeContext } from "$lib/components/contexts/TimeContext.svelte";
|
||||||
|
import ItemListRow from "$lib/components/scope/ItemListRow.svelte";
|
||||||
|
import Icon from "$lib/components/layout/Icon.svelte";
|
||||||
|
|
||||||
export let acquiredItems: Item[];
|
export let acquiredItems: Item[];
|
||||||
export let scheduledItems: Item[];
|
export let scheduledItems: Item[];
|
||||||
|
export let looseItems: Item[];
|
||||||
export let sprints: Sprint[];
|
export let sprints: Sprint[];
|
||||||
|
|
||||||
const {scope} = getScopeContext();
|
const {scope} = getScopeContext();
|
||||||
const {openModal} = getModalContext();
|
const {openModal} = getModalContext();
|
||||||
|
const {now} = getTimeContext();
|
||||||
|
|
||||||
function openCreateProject() {
|
function openCreateProject() {
|
||||||
openModal({name: "project.create"});
|
openModal({name: "project.create"});
|
||||||
@@ -73,53 +90,44 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SprintListContext sprints={sprints} intervalString="">
|
<ItemMultiListContext lists={{acquiredItems, scheduledItems, looseItems}} filters={generateItemFilters($now)}>
|
||||||
<Columns fullwidth>
|
<SprintListContext sprints={sprints} intervalString="">
|
||||||
<Column>
|
<Columns fullwidth>
|
||||||
<Row title="Options">
|
<Column>
|
||||||
<Card on:click={openCreateProject} pointerCursor><CardHeader>Create Project</CardHeader></Card>
|
<Row title="Options">
|
||||||
<Card on:click={openPostItem} pointerCursor><CardHeader>Post Item</CardHeader></Card>
|
<Card on:click={openCreateProject} pointerCursor><CardHeader>Create Project</CardHeader></Card>
|
||||||
<Card on:click={openEditScope} pointerCursor><CardHeader>Edit Scope</CardHeader></Card>
|
<Card on:click={openPostItem} pointerCursor><CardHeader>Create Item</CardHeader></Card>
|
||||||
<Card on:click={openDeleteScope} pointerCursor><CardHeader>Delete Scope</CardHeader></Card>
|
<Card on:click={openEditScope} pointerCursor><CardHeader>Edit Scope</CardHeader></Card>
|
||||||
</Row>
|
<Card on:click={openDeleteScope} pointerCursor><CardHeader>Delete Scope</CardHeader></Card>
|
||||||
{#if scheduledItems.length > 0}
|
</Row>
|
||||||
<Row title="Schedule">
|
<ItemListRow title="Scheduled" key="scheduleItems" />
|
||||||
{#each scheduledItems as item (item.id)}
|
<ItemListRow title="Today" key="acquiredItems" showAcquiredTime />
|
||||||
<ItemSubSection item={item} />
|
<ItemListRow title="Loose" key="looseItems" />
|
||||||
|
</Column>
|
||||||
|
<Column>
|
||||||
|
<Row title="Sprints">
|
||||||
|
<OptionsRow slot="right">
|
||||||
|
<Option open={{name: "sprint.create"}}><Icon name="plus" /></Option>
|
||||||
|
</OptionsRow>
|
||||||
|
<SprintList sub />
|
||||||
|
</Row>
|
||||||
|
<Row title="Stats">
|
||||||
|
<OptionsRow slot="right">
|
||||||
|
<Option open={{name: "stat.create"}}><Icon name="plus" /></Option>
|
||||||
|
</OptionsRow>
|
||||||
|
{#each $scope.stats as stat (stat.id)}
|
||||||
|
<StatSubSection stat={stat} />
|
||||||
{/each}
|
{/each}
|
||||||
</Row>
|
</Row>
|
||||||
{/if}
|
</Column>
|
||||||
{#if acquiredItems.length > 0}
|
</Columns>
|
||||||
<Row title="Today">
|
|
||||||
{#each acquiredItems as item (item.id)}
|
<ItemCreateModal />
|
||||||
<ItemSubSection item={item} />
|
<ItemAcquireModal />
|
||||||
{/each}
|
<StatCreateEditModal />
|
||||||
</Row>
|
<DeletionModal />
|
||||||
{/if}
|
<SprintCreateUpdateModal />
|
||||||
</Column>
|
<ProjectCreateEditModal />
|
||||||
<Column>
|
<ScopeCreateUpdateModal />
|
||||||
<Row title="Sprints">
|
</SprintListContext>
|
||||||
<OptionsRow slot="right">
|
</ItemMultiListContext>
|
||||||
<Option open={{name: "sprint.create"}}>Create</Option>
|
|
||||||
</OptionsRow>
|
|
||||||
<SprintList sub />
|
|
||||||
</Row>
|
|
||||||
<Row title="Stats">
|
|
||||||
<OptionsRow slot="right">
|
|
||||||
<Option open={{name: "stat.create"}}>Create</Option>
|
|
||||||
</OptionsRow>
|
|
||||||
{#each $scope.stats as stat (stat.id)}
|
|
||||||
<StatSubSection stat={stat} />
|
|
||||||
{/each}
|
|
||||||
</Row>
|
|
||||||
</Column>
|
|
||||||
</Columns>
|
|
||||||
|
|
||||||
<ItemCreateModal />
|
|
||||||
<ItemAcquireModal />
|
|
||||||
<StatCreateEditModal />
|
|
||||||
<DeletionModal />
|
|
||||||
<SprintCreateUpdateModal />
|
|
||||||
<ProjectCreateEditModal />
|
|
||||||
<ScopeCreateUpdateModal />
|
|
||||||
</SprintListContext>
|
|
||||||
@@ -2,11 +2,34 @@
|
|||||||
import type { Load } from "@sveltejs/kit/types/internal";
|
import type { Load } from "@sveltejs/kit/types/internal";
|
||||||
import { sl3 } from "$lib/clients/sl3";
|
import { sl3 } from "$lib/clients/sl3";
|
||||||
|
|
||||||
export const load: Load = async({ fetch, session }) => {
|
function generateItemFilters(now: Date): {scheduledFilter: ItemFilter, acquiredFilter: ItemFilter, looseFilter: ItemFilter} {
|
||||||
|
return {
|
||||||
|
scheduledFilter: {
|
||||||
|
scheduledDate: datesOf(parseInterval("next:7d", now)),
|
||||||
|
},
|
||||||
|
acquiredFilter: {
|
||||||
|
acquiredTime: morningInterval(parseInterval("today", now)),
|
||||||
|
},
|
||||||
|
looseFilter: {
|
||||||
|
loose: true,
|
||||||
|
unAcquired: true,
|
||||||
|
unScheduled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const load: Load = async({ fetch }) => {
|
||||||
const scopes = await sl3(fetch).listScopes();
|
const scopes = await sl3(fetch).listScopes();
|
||||||
|
|
||||||
|
const {acquiredFilter, looseFilter, scheduledFilter} = generateItemFilters(new Date());
|
||||||
|
|
||||||
|
const scheduledItems = (await sl3(fetch).listItems("ALL", scheduledFilter));
|
||||||
|
const acquiredItems = (await sl3(fetch).listItems("ALL", acquiredFilter));
|
||||||
|
const looseItems = (await sl3(fetch).listItems("ALL", looseFilter));
|
||||||
|
const sprints = await sl3(fetch).listSprints("ALL");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: { scopes }
|
props: { scopes, scheduledItems, acquiredItems, looseItems, sprints }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -21,26 +44,60 @@
|
|||||||
import ScopeCreateUpdateModal from "$lib/modals/ScopeCreateUpdateModal.svelte";
|
import ScopeCreateUpdateModal from "$lib/modals/ScopeCreateUpdateModal.svelte";
|
||||||
import OptionsRow from "$lib/components/layout/OptionsRow.svelte";
|
import OptionsRow from "$lib/components/layout/OptionsRow.svelte";
|
||||||
import Option from "$lib/components/layout/Option.svelte";
|
import Option from "$lib/components/layout/Option.svelte";
|
||||||
import ScopeListContext from "$lib/components/contexts/ScopeListContext.svelte";
|
import ScopeListContext from "$lib/components/contexts/ScopeListContext.svelte";
|
||||||
|
import parseInterval, { datesOf, morningInterval } from "$lib/utils/timeinterval";
|
||||||
|
import ItemMultiListContext from "$lib/components/contexts/ItemMultiListContext.svelte";
|
||||||
|
import type Item from "$lib/models/item";
|
||||||
|
import DeletionModal from "$lib/modals/DeletionModal.svelte";
|
||||||
|
import ItemAcquireModal from "$lib/modals/ItemAcquireModal.svelte";
|
||||||
|
import ItemListRow from "$lib/components/scope/ItemListRow.svelte";
|
||||||
|
import { getTimeContext } from "$lib/components/contexts/TimeContext.svelte";
|
||||||
|
import type { ItemFilter } from "$lib/models/item";
|
||||||
|
import ItemCreateModal from "$lib/modals/ItemCreateModal.svelte";
|
||||||
|
import SprintList from "$lib/components/scope/SprintList.svelte";
|
||||||
|
import SprintCreateUpdateModal from "$lib/modals/SprintCreateUpdateModal.svelte";
|
||||||
|
import SprintListContext from "$lib/components/contexts/SprintListContext.svelte";
|
||||||
|
import type Sprint from "$lib/models/sprint";
|
||||||
|
import Icon from "$lib/components/layout/Icon.svelte";
|
||||||
|
|
||||||
export let scopes: Scope[] = [];
|
export let scopes: Scope[] = [];
|
||||||
|
export let acquiredItems: Item[];
|
||||||
|
export let scheduledItems: Item[];
|
||||||
|
export let looseItems: Item[];
|
||||||
|
export let sprints: Sprint[];
|
||||||
|
|
||||||
|
const {now} = getTimeContext();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Header fullwidth wide subtitle="Logging your stuff">Stufflog</Header>
|
<Header fullwidth wide subtitle="Logging your stuff">Stufflog</Header>
|
||||||
|
|
||||||
<ScopeListContext scopes={scopes}>
|
<ItemMultiListContext global lists={{acquiredItems, scheduledItems, looseItems}} filters={generateItemFilters($now)}>
|
||||||
<Columns wide>
|
<ScopeListContext scopes={scopes}>
|
||||||
<Column>
|
<SprintListContext global sprints={sprints} intervalString="">
|
||||||
<Row title="Scopes">
|
<Columns wide>
|
||||||
<OptionsRow slot="right">
|
<Column>
|
||||||
<Option open={{name: "scope.create"}}>Create</Option>
|
<Row title="Scopes">
|
||||||
</OptionsRow>
|
<OptionsRow slot="right">
|
||||||
<ScopeLinkList />
|
<Option open={{name: "scope.create"}}><Icon name="plus" /></Option>
|
||||||
</Row>
|
</OptionsRow>
|
||||||
</Column>
|
<ScopeLinkList />
|
||||||
<Column>
|
</Row>
|
||||||
</Column>
|
<ItemListRow title="Scheduled" key="scheduleItems" />
|
||||||
</Columns>
|
<ItemListRow title="Today" key="acquiredItems" showAcquiredTime />
|
||||||
|
<ItemListRow title="Loose" key="looseItems" />
|
||||||
<ScopeCreateUpdateModal />
|
</Column>
|
||||||
</ScopeListContext>
|
<Column>
|
||||||
|
<Row title="Sprints">
|
||||||
|
<SprintList sub />
|
||||||
|
</Row>
|
||||||
|
</Column>
|
||||||
|
</Columns>
|
||||||
|
|
||||||
|
<ItemCreateModal />
|
||||||
|
<ItemAcquireModal />
|
||||||
|
<DeletionModal />
|
||||||
|
<ScopeCreateUpdateModal />
|
||||||
|
<SprintCreateUpdateModal />
|
||||||
|
</SprintListContext>
|
||||||
|
</ScopeListContext>
|
||||||
|
</ItemMultiListContext>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 495 KiB After Width: | Height: | Size: 112 KiB |
@@ -4,17 +4,18 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/squirrel v1.5.2
|
github.com/Masterminds/squirrel v1.5.2
|
||||||
|
github.com/aws/aws-lambda-go v1.19.1
|
||||||
github.com/aws/aws-sdk-go v1.44.27
|
github.com/aws/aws-sdk-go v1.44.27
|
||||||
|
github.com/awslabs/aws-lambda-go-api-proxy v0.13.3
|
||||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1
|
||||||
github.com/gin-gonic/gin v1.7.7
|
github.com/gin-gonic/gin v1.7.7
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/lestrrat-go/jwx v1.2.25
|
github.com/lestrrat-go/jwx v1.2.25
|
||||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-lambda-go v1.19.1 // indirect
|
|
||||||
github.com/awslabs/aws-lambda-go-api-proxy v0.13.3 // indirect
|
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.13.0 // indirect
|
github.com/go-playground/locales v0.13.0 // indirect
|
||||||
@@ -36,7 +37,6 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/snabb/isoweek v1.0.1 // indirect
|
|
||||||
github.com/ugorji/go/codec v1.1.7 // indirect
|
github.com/ugorji/go/codec v1.1.7 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
|
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+
|
|||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||||
|
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
@@ -163,7 +164,6 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K
|
|||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
@@ -195,6 +195,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
@@ -258,7 +259,6 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW
|
|||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
@@ -285,8 +285,10 @@ github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
|
|||||||
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ=
|
github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ=
|
||||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
@@ -320,7 +322,6 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb
|
|||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
@@ -337,11 +338,9 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
|
|||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
@@ -356,16 +355,19 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
|
|||||||
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
|
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
@@ -399,8 +401,6 @@ github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl
|
|||||||
github.com/smartystreets/assertions v1.2.1/go.mod h1:wDmR7qL282YbGsPy6H/yAsesrxfxaaSlJazyFLYVFx8=
|
github.com/smartystreets/assertions v1.2.1/go.mod h1:wDmR7qL282YbGsPy6H/yAsesrxfxaaSlJazyFLYVFx8=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
||||||
github.com/snabb/isoweek v1.0.1 h1:B4IsN2GU8lCNVkaUUgOzaVpPkKC2DdY9zcnxz5yc0qg=
|
|
||||||
github.com/snabb/isoweek v1.0.1/go.mod h1:CAijAxH7NMgjqGc9baHMDE4sTHMt4B/f6X/XLiEE1iA=
|
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
@@ -489,7 +489,6 @@ golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm
|
|||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
|
|
||||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
@@ -573,6 +572,7 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
|
||||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -663,7 +663,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -681,6 +680,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -744,6 +744,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
@@ -850,7 +851,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
|||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -862,16 +862,15 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
|||||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package genutils
|
||||||
|
|
||||||
|
func Flatten2[T any](arrs [][]T) []T {
|
||||||
|
res := make([]T, 0, 128)
|
||||||
|
for _, arr := range arrs {
|
||||||
|
res = append(res, arr...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
@@ -8,3 +8,12 @@ func Map[I any, O any](arr []I, cb func(a I) O) []O {
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MapValues[K comparable, V any](m map[K]V) []V {
|
||||||
|
res := make([]V, 0, len(m))
|
||||||
|
for _, v := range m {
|
||||||
|
res = append(res, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,6 +46,14 @@ func (d Date) ToTime() time.Time {
|
|||||||
return time.Date(d[0], time.Month(d[1]), d[2], 0, 0, 0, 0, time.UTC)
|
return time.Date(d[0], time.Month(d[1]), d[2], 0, 0, 0, 0, time.UTC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d Date) WithTime(hh, mm, ss int) time.Time {
|
||||||
|
return d.WithTimeAndLocation(hh, mm, ss, time.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Date) WithTimeAndLocation(hh, mm, ss int, loc *time.Location) time.Time {
|
||||||
|
return time.Date(d[0], time.Month(d[1]), d[2], hh, mm, ss, 0, loc)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Date) UnmarshalJSON(b []byte) error {
|
func (d *Date) UnmarshalJSON(b []byte) error {
|
||||||
var str string
|
var str string
|
||||||
err := json.Unmarshal(b, &str)
|
err := json.Unmarshal(b, &str)
|
||||||
|
|||||||
@@ -25,4 +25,6 @@ type ItemFilter struct {
|
|||||||
RequirementIDs []int `json:"requirementIds,omitempty"`
|
RequirementIDs []int `json:"requirementIds,omitempty"`
|
||||||
StatIDs []int `json:"statIds,omitempty"`
|
StatIDs []int `json:"statIds,omitempty"`
|
||||||
Loose bool `json:"loose,omitempty"`
|
Loose bool `json:"loose,omitempty"`
|
||||||
|
UnScheduled bool `json:"unScheduled,omitempty"`
|
||||||
|
UnAcquired bool `json:"unAcquired,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -8,8 +8,9 @@ type ProjectUpdate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RequirementUpdate struct {
|
type RequirementUpdate struct {
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
Description *string `json:"description"`
|
Description *string `json:"description"`
|
||||||
Status *Status `json:"status"`
|
Status *Status `json:"status"`
|
||||||
IsCoarse *bool `json:"isCoarse"`
|
IsCoarse *bool `json:"isCoarse"`
|
||||||
|
AggregateRequired *int `json:"aggregateRequired"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,44 @@ func (s Status) Name() string {
|
|||||||
return StatusLabels[s]
|
return StatusLabels[s]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Status) Less(s2 Status) bool {
|
||||||
|
if s == Active && s2 != Active {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s != Active && s2 == Active {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s == Background && s2 != Background {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s != Background && s2 == Background {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s == Available && s2 != Available {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s != Available && s2 == Available {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s == Blocked && s2 != Blocked {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if s != Blocked && s2 == Blocked {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s < s2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Status) Ended() bool {
|
||||||
|
switch s {
|
||||||
|
case Completed, Failed, Dropped:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Blocked Status = iota
|
Blocked Status = iota
|
||||||
Available
|
Available
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ func getterHandler[O any](key, idKey string, callback func(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handler(key string, callback func(c *gin.Context) (interface{}, error)) gin.HandlerFunc {
|
func handler(key string, callback func(c *gin.Context) (interface{}, error)) gin.HandlerFunc {
|
||||||
|
type overrider interface {
|
||||||
|
OverrideJSONResponse() bool
|
||||||
|
}
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
res, err := callback(c)
|
res, err := callback(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -82,6 +86,11 @@ func handler(key string, callback func(c *gin.Context) (interface{}, error)) gin
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o, ok := res.(overrider); ok && o.OverrideJSONResponse() {
|
||||||
|
c.JSON(200, res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resJson := make(map[string]interface{}, 1)
|
resJson := make(map[string]interface{}, 1)
|
||||||
resJson[key] = res
|
resJson[key] = res
|
||||||
|
|
||||||
|
|||||||
+254
-193
@@ -12,209 +12,75 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Items(g *gin.RouterGroup, items *items.Service) {
|
type groupedRes struct {
|
||||||
|
Items []items.Result `json:"items"`
|
||||||
|
Groups []items.Group `json:"groups"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g groupedRes) OverrideJSONResponse() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ItemsAllScopes(g *gin.RouterGroup, itemService *items.Service) {
|
||||||
|
g.GET("", handler("items", func(c *gin.Context) (interface{}, error) {
|
||||||
|
filter, err := getItemFilterFromQuery(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := itemService.ListAllScopes(c.Request.Context(), filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Query("grouped") == "true" {
|
||||||
|
return groupedRes{
|
||||||
|
Items: res,
|
||||||
|
Groups: items.GenerateGroups(res, items.GroupOptions{
|
||||||
|
HideCreated: c.Query("hideCreated") == "true",
|
||||||
|
HideAcquired: c.Query("hideAcquired") == "true",
|
||||||
|
HideScheduled: c.Query("hideScheduled") == "true",
|
||||||
|
}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Items(g *gin.RouterGroup, itemService *items.Service) {
|
||||||
g.GET("/:item_id", handler("project", func(c *gin.Context) (interface{}, error) {
|
g.GET("/:item_id", handler("project", func(c *gin.Context) (interface{}, error) {
|
||||||
id, err := reqInt(c, "item_id")
|
id, err := reqInt(c, "item_id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.Find(c.Request.Context(), id)
|
return itemService.Find(c.Request.Context(), id)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g.GET("", handler("items", func(c *gin.Context) (interface{}, error) {
|
g.GET("", handler("items", func(c *gin.Context) (interface{}, error) {
|
||||||
filter := models.ItemFilter{}
|
filter, err := getItemFilterFromQuery(c)
|
||||||
|
if err != nil {
|
||||||
if rawFilter := c.Query("filter"); rawFilter != "" {
|
return nil, err
|
||||||
err := json.Unmarshal([]byte(rawFilter), &filter)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "filter",
|
|
||||||
Problem: "Invalid raw filter: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if queryOwnerId := c.Query("ownerId"); queryOwnerId != "" {
|
res, err := itemService.ListScoped(c.Request.Context(), filter)
|
||||||
filter.OwnerID = &queryOwnerId
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
queryScheduledMin := c.Query("scheduledMin")
|
if c.Query("grouped") == "true" {
|
||||||
queryScheduledMax := c.Query("scheduledMax")
|
return groupedRes{
|
||||||
if queryScheduledMin != "" && queryScheduledMax != "" {
|
Items: res,
|
||||||
min, err := models.ParseDate(queryScheduledMin)
|
Groups: items.GenerateGroups(res, items.GroupOptions{
|
||||||
if err != nil {
|
HideCreated: c.Query("hideCreated") == "true",
|
||||||
return nil, models.BadInputError{
|
HideAcquired: c.Query("hideAcquired") == "true",
|
||||||
Object: "Query",
|
HideScheduled: c.Query("hideScheduled") == "true",
|
||||||
Field: "scheduledMin",
|
}),
|
||||||
Problem: "Invalid from date: " + err.Error(),
|
}, nil
|
||||||
}
|
|
||||||
}
|
|
||||||
max, err := models.ParseDate(queryScheduledMax)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "scheduledMax",
|
|
||||||
Problem: "Invalid to date: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.ScheduledDate = &models.TimeInterval[models.Date]{Min: min, Max: max}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
queryCreatedMin := c.Query("createdMin")
|
return res, nil
|
||||||
queryCreatedMax := c.Query("createdMax")
|
|
||||||
if queryCreatedMin != "" && queryCreatedMax != "" {
|
|
||||||
min, err := time.Parse(time.RFC3339, queryCreatedMin)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "createdMin",
|
|
||||||
Problem: "Invalid from date: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max, err := time.Parse(time.RFC3339, queryCreatedMax)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "createdMax",
|
|
||||||
Problem: "Invalid to date: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.CreatedTime = &models.TimeInterval[time.Time]{Min: min, Max: max}
|
|
||||||
}
|
|
||||||
|
|
||||||
queryAcquiredMin := c.Query("acquiredMin")
|
|
||||||
queryAcquiredMax := c.Query("acquiredMax")
|
|
||||||
if queryAcquiredMin != "" && queryAcquiredMax != "" {
|
|
||||||
min, err := time.Parse(time.RFC3339, queryAcquiredMin)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "acquiredMin",
|
|
||||||
Problem: "Invalid from date: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max, err := time.Parse(time.RFC3339, queryAcquiredMax)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "acquiredMax",
|
|
||||||
Problem: "Invalid to date: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.AcquiredTime = &models.TimeInterval[time.Time]{Min: min, Max: max}
|
|
||||||
}
|
|
||||||
|
|
||||||
anyDateMin := c.Query("anyDateMin")
|
|
||||||
anyDateMax := c.Query("anyDateMax")
|
|
||||||
if anyDateMin != "" && anyDateMax != "" {
|
|
||||||
min, err := time.Parse(time.RFC3339, anyDateMin)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "anyDateMin",
|
|
||||||
Problem: "Invalid from date: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max, err := time.Parse(time.RFC3339, anyDateMax)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "anyDateMax",
|
|
||||||
Problem: "Invalid to date: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
minY, minM, minD := min.Date()
|
|
||||||
maxY, maxM, maxD := max.Date()
|
|
||||||
|
|
||||||
filter.AcquiredTime = &models.TimeInterval[time.Time]{Min: min, Max: max}
|
|
||||||
filter.CreatedTime = &models.TimeInterval[time.Time]{Min: min, Max: max}
|
|
||||||
filter.ScheduledDate = &models.TimeInterval[models.Date]{
|
|
||||||
Min: models.Date{minY, int(minM), minD},
|
|
||||||
Max: models.Date{maxY, int(maxM), maxD},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if queryProjectID := c.Query("projectId"); queryProjectID != "" {
|
|
||||||
ids := strings.Split(queryProjectID, ",")
|
|
||||||
for _, id := range ids {
|
|
||||||
parsed, err := strconv.Atoi(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: fmt.Sprintf("projectId[%d]", len(filter.ProjectIDs)),
|
|
||||||
Problem: "Invalid number",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.ProjectIDs = append(filter.ProjectIDs, parsed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if queryRequirementID := c.Query("requirementId"); queryRequirementID != "" {
|
|
||||||
if queryRequirementID != "null" {
|
|
||||||
ids := strings.Split(queryRequirementID, ",")
|
|
||||||
for _, id := range ids {
|
|
||||||
parsed, err := strconv.Atoi(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: fmt.Sprintf("requirementId[%d]", len(filter.RequirementIDs)),
|
|
||||||
Problem: "Invalid number",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.RequirementIDs = append(filter.RequirementIDs, parsed)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
filter.Loose = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if statID := c.Query("statId"); statID != "" {
|
|
||||||
ids := strings.Split(statID, ",")
|
|
||||||
for _, id := range ids {
|
|
||||||
parsed, err := strconv.Atoi(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: fmt.Sprintf("statId[%d]", len(filter.StatIDs)),
|
|
||||||
Problem: "Invalid number",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.StatIDs = append(filter.StatIDs, parsed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if filter.ScheduledDate != nil && !filter.ScheduledDate.Valid() {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Filter",
|
|
||||||
Field: "scheduledDate",
|
|
||||||
Problem: "Invalid scheduled date range",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if filter.CreatedTime != nil && !filter.CreatedTime.Valid() {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Filter",
|
|
||||||
Field: "createdTime",
|
|
||||||
Problem: "Invalid created time range",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if filter.AcquiredTime != nil && !filter.AcquiredTime.Valid() {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Filter",
|
|
||||||
Field: "acquiredTime",
|
|
||||||
Problem: "Invalid created time range",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items.ListScoped(c.Request.Context(), filter)
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g.POST("", handler("item", func(c *gin.Context) (interface{}, error) {
|
g.POST("", handler("item", func(c *gin.Context) (interface{}, error) {
|
||||||
@@ -230,7 +96,7 @@ func Items(g *gin.RouterGroup, items *items.Service) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.Create(c.Request.Context(), input.Item, input.Stats)
|
return itemService.Create(c.Request.Context(), input.Item, input.Stats)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g.PUT("/:item_id", handler("item", func(c *gin.Context) (interface{}, error) {
|
g.PUT("/:item_id", handler("item", func(c *gin.Context) (interface{}, error) {
|
||||||
@@ -251,7 +117,7 @@ func Items(g *gin.RouterGroup, items *items.Service) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.Update(c.Request.Context(), id, input.ItemUpdate, input.Stats)
|
return itemService.Update(c.Request.Context(), id, input.ItemUpdate, input.Stats)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g.PUT("/:item_id/stats/:stat_id", handler("item", func(c *gin.Context) (interface{}, error) {
|
g.PUT("/:item_id/stats/:stat_id", handler("item", func(c *gin.Context) (interface{}, error) {
|
||||||
@@ -275,7 +141,7 @@ func Items(g *gin.RouterGroup, items *items.Service) {
|
|||||||
|
|
||||||
input.StatID = statID
|
input.StatID = statID
|
||||||
|
|
||||||
return items.UpdateStat(c.Request.Context(), itemID, input)
|
return itemService.UpdateStat(c.Request.Context(), itemID, input)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g.DELETE("/:item_id/stats/:stat_id", handler("item", func(c *gin.Context) (interface{}, error) {
|
g.DELETE("/:item_id/stats/:stat_id", handler("item", func(c *gin.Context) (interface{}, error) {
|
||||||
@@ -288,7 +154,7 @@ func Items(g *gin.RouterGroup, items *items.Service) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.DeleteStat(c.Request.Context(), itemID, statID)
|
return itemService.DeleteStat(c.Request.Context(), itemID, statID)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g.DELETE("/:item_id", handler("item", func(c *gin.Context) (interface{}, error) {
|
g.DELETE("/:item_id", handler("item", func(c *gin.Context) (interface{}, error) {
|
||||||
@@ -297,6 +163,201 @@ func Items(g *gin.RouterGroup, items *items.Service) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.Delete(c.Request.Context(), id)
|
return itemService.Delete(c.Request.Context(), id)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getItemFilterFromQuery(c *gin.Context) (models.ItemFilter, error) {
|
||||||
|
filter := models.ItemFilter{}
|
||||||
|
|
||||||
|
if rawFilter := c.Query("filter"); rawFilter != "" {
|
||||||
|
err := json.Unmarshal([]byte(rawFilter), &filter)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "filter",
|
||||||
|
Problem: "Invalid raw filter: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryOwnerId := c.Query("ownerId"); queryOwnerId != "" {
|
||||||
|
filter.OwnerID = &queryOwnerId
|
||||||
|
}
|
||||||
|
|
||||||
|
queryScheduledMin := c.Query("scheduledMin")
|
||||||
|
queryScheduledMax := c.Query("scheduledMax")
|
||||||
|
if queryScheduledMin != "" && queryScheduledMax != "" {
|
||||||
|
min, err := models.ParseDate(queryScheduledMin)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "scheduledMin",
|
||||||
|
Problem: "Invalid from date: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max, err := models.ParseDate(queryScheduledMax)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "scheduledMax",
|
||||||
|
Problem: "Invalid to date: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.ScheduledDate = &models.TimeInterval[models.Date]{Min: min, Max: max}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryCreatedMin := c.Query("createdMin")
|
||||||
|
queryCreatedMax := c.Query("createdMax")
|
||||||
|
if queryCreatedMin != "" && queryCreatedMax != "" {
|
||||||
|
min, err := time.Parse(time.RFC3339, queryCreatedMin)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "createdMin",
|
||||||
|
Problem: "Invalid from date: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max, err := time.Parse(time.RFC3339, queryCreatedMax)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "createdMax",
|
||||||
|
Problem: "Invalid to date: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.CreatedTime = &models.TimeInterval[time.Time]{Min: min, Max: max}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryAcquiredMin := c.Query("acquiredMin")
|
||||||
|
queryAcquiredMax := c.Query("acquiredMax")
|
||||||
|
if queryAcquiredMin != "" && queryAcquiredMax != "" {
|
||||||
|
min, err := time.Parse(time.RFC3339, queryAcquiredMin)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "acquiredMin",
|
||||||
|
Problem: "Invalid from date: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max, err := time.Parse(time.RFC3339, queryAcquiredMax)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "acquiredMax",
|
||||||
|
Problem: "Invalid to date: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.AcquiredTime = &models.TimeInterval[time.Time]{Min: min, Max: max}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyDateMin := c.Query("anyDateMin")
|
||||||
|
anyDateMax := c.Query("anyDateMax")
|
||||||
|
if anyDateMin != "" && anyDateMax != "" {
|
||||||
|
min, err := time.Parse(time.RFC3339, anyDateMin)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "anyDateMin",
|
||||||
|
Problem: "Invalid from date: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
max, err := time.Parse(time.RFC3339, anyDateMax)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "anyDateMax",
|
||||||
|
Problem: "Invalid to date: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minY, minM, minD := min.Date()
|
||||||
|
maxY, maxM, maxD := max.Date()
|
||||||
|
|
||||||
|
filter.AcquiredTime = &models.TimeInterval[time.Time]{Min: min, Max: max}
|
||||||
|
filter.CreatedTime = &models.TimeInterval[time.Time]{Min: min, Max: max}
|
||||||
|
filter.ScheduledDate = &models.TimeInterval[models.Date]{
|
||||||
|
Min: models.Date{minY, int(minM), minD},
|
||||||
|
Max: models.Date{maxY, int(maxM), maxD},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryProjectID := c.Query("projectId"); queryProjectID != "" {
|
||||||
|
ids := strings.Split(queryProjectID, ",")
|
||||||
|
for _, id := range ids {
|
||||||
|
parsed, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: fmt.Sprintf("projectId[%d]", len(filter.ProjectIDs)),
|
||||||
|
Problem: "Invalid number",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.ProjectIDs = append(filter.ProjectIDs, parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if queryRequirementID := c.Query("requirementId"); queryRequirementID != "" {
|
||||||
|
if queryRequirementID != "null" {
|
||||||
|
ids := strings.Split(queryRequirementID, ",")
|
||||||
|
for _, id := range ids {
|
||||||
|
parsed, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: fmt.Sprintf("requirementId[%d]", len(filter.RequirementIDs)),
|
||||||
|
Problem: "Invalid number",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.RequirementIDs = append(filter.RequirementIDs, parsed)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filter.Loose = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if statID := c.Query("statId"); statID != "" {
|
||||||
|
ids := strings.Split(statID, ",")
|
||||||
|
for _, id := range ids {
|
||||||
|
parsed, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: fmt.Sprintf("statId[%d]", len(filter.StatIDs)),
|
||||||
|
Problem: "Invalid number",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.StatIDs = append(filter.StatIDs, parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.ScheduledDate != nil && !filter.ScheduledDate.Valid() {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Filter",
|
||||||
|
Field: "scheduledDate",
|
||||||
|
Problem: "Invalid scheduled date range",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if filter.CreatedTime != nil && !filter.CreatedTime.Valid() {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Filter",
|
||||||
|
Field: "createdTime",
|
||||||
|
Problem: "Invalid created time range",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if filter.AcquiredTime != nil && !filter.AcquiredTime.Valid() {
|
||||||
|
return models.ItemFilter{}, models.BadInputError{
|
||||||
|
Object: "Filter",
|
||||||
|
Field: "acquiredTime",
|
||||||
|
Problem: "Invalid created time range",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter, nil
|
||||||
|
}
|
||||||
|
|||||||
+28
-24
@@ -9,20 +9,40 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type scopeResult struct {
|
func AllScopeMiddleware(scopes *scopes.Service, auth *auth.Service) gin.HandlerFunc {
|
||||||
entities.Scope
|
return func(c *gin.Context) {
|
||||||
|
user := auth.GetUser(c.Request.Context())
|
||||||
|
if user == nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, Error{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
Message: "Scopes not found or inaccessible",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Members []scopeResultMember `json:"members"`
|
userScopes, err := scopes.List(c.Request.Context())
|
||||||
}
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, Error{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
Message: "Scopes not found or inaccessible",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type scopeResultMember struct {
|
c.Request = c.Request.WithContext(scopes.CreateListContext(c.Request.Context(), user.ID, userScopes))
|
||||||
ID string `json:"id"`
|
}
|
||||||
Name string `json:"name"`
|
|
||||||
Owner bool `json:"owner"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ScopeMiddleware(scopes *scopes.Service, auth *auth.Service) gin.HandlerFunc {
|
func ScopeMiddleware(scopes *scopes.Service, auth *auth.Service) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
user := auth.GetUser(c.Request.Context())
|
||||||
|
if user == nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, Error{
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
Message: "Scope not found or inaccessible",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
id, err := reqInt(c, "scope_id")
|
id, err := reqInt(c, "scope_id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, Error{
|
c.AbortWithStatusJSON(http.StatusUnauthorized, Error{
|
||||||
@@ -45,22 +65,6 @@ func ScopeMiddleware(scopes *scopes.Service, auth *auth.Service) gin.HandlerFunc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
|
||||||
user := auth.GetUser(c.Request.Context())
|
|
||||||
for id, _ := range scope.Members {
|
|
||||||
if id == user.ID {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, Error{
|
|
||||||
Code: http.StatusNotFound,
|
|
||||||
Message: "Scope not found or inaccessible",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Request = c.Request.WithContext(scopes.CreateContext(c.Request.Context(), user.ID, *scope))
|
c.Request = c.Request.WithContext(scopes.CreateContext(c.Request.Context(), user.ID, *scope))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-24
@@ -9,34 +9,22 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Sprints(g *gin.RouterGroup, sprintsService *sprints.Service) {
|
func SprintsAllScopes(g *gin.RouterGroup, sprintsService *sprints.Service) {
|
||||||
g.GET("", handler("sprints", func(c *gin.Context) (interface{}, error) {
|
g.GET("", handler("sprints", func(c *gin.Context) (interface{}, error) {
|
||||||
from := time.Now().Add(-time.Hour * 24 * 7)
|
from, to, err := getFromToFromQuery(c)
|
||||||
if fromQuery := c.Query("from"); fromQuery != "" {
|
if err != nil {
|
||||||
parsed, err := time.Parse(time.RFC3339Nano, fromQuery)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "from",
|
|
||||||
Problem: "Incorrect date format: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
from = parsed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
to := time.Now().Add(time.Hour * 24 * 7)
|
return sprintsService.ListBetween(c.Request.Context(), from, to)
|
||||||
if toQuery := c.Query("to"); toQuery != "" {
|
}))
|
||||||
parsed, err := time.Parse(time.RFC3339Nano, toQuery)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, models.BadInputError{
|
|
||||||
Object: "Query",
|
|
||||||
Field: "to",
|
|
||||||
Problem: "Incorrect date format: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
to = parsed
|
func Sprints(g *gin.RouterGroup, sprintsService *sprints.Service) {
|
||||||
|
g.GET("", handler("sprints", func(c *gin.Context) (interface{}, error) {
|
||||||
|
from, to, err := getFromToFromQuery(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintsService.ListScopedBetween(c.Request.Context(), from, to)
|
return sprintsService.ListScopedBetween(c.Request.Context(), from, to)
|
||||||
@@ -126,3 +114,35 @@ func Sprints(g *gin.RouterGroup, sprintsService *sprints.Service) {
|
|||||||
return sprintsService.Delete(c.Request.Context(), sprintID)
|
return sprintsService.Delete(c.Request.Context(), sprintID)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFromToFromQuery(c *gin.Context) (time.Time, time.Time, error) {
|
||||||
|
from := time.Now().Add(-time.Hour * 24 * 7)
|
||||||
|
if fromQuery := c.Query("from"); fromQuery != "" {
|
||||||
|
parsed, err := time.Parse(time.RFC3339Nano, fromQuery)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, time.Time{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "from",
|
||||||
|
Problem: "Incorrect date format: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
from = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
to := time.Now().Add(time.Hour * 24 * 7)
|
||||||
|
if toQuery := c.Query("to"); toQuery != "" {
|
||||||
|
parsed, err := time.Parse(time.RFC3339Nano, toQuery)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, time.Time{}, models.BadInputError{
|
||||||
|
Object: "Query",
|
||||||
|
Field: "to",
|
||||||
|
Problem: "Incorrect date format: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
to = parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
return from, to, nil
|
||||||
|
}
|
||||||
|
|||||||
+2
-6
@@ -1,7 +1,6 @@
|
|||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -75,11 +74,8 @@ func Connect(host string, port int, username, password, database string) (*Datab
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
q, err := mysqlcore.Prepare(context.Background(), db)
|
q := mysqlcore.New(db)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Database{db: db, q: q}, nil
|
return &Database{db: db, q: q}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,13 +67,17 @@ func (r *itemRepository) Fetch(ctx context.Context, filter models.ItemFilter) ([
|
|||||||
squirrel.Lt{"i.created_time": filter.CreatedTime.Max},
|
squirrel.Lt{"i.created_time": filter.CreatedTime.Max},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if filter.AcquiredTime != nil {
|
if filter.UnAcquired {
|
||||||
|
sq = sq.Where("i.acquired_time IS NULL")
|
||||||
|
} else if filter.AcquiredTime != nil {
|
||||||
dateOr = append(dateOr, squirrel.And{
|
dateOr = append(dateOr, squirrel.And{
|
||||||
squirrel.GtOrEq{"i.acquired_time": filter.AcquiredTime.Min},
|
squirrel.GtOrEq{"i.acquired_time": filter.AcquiredTime.Min},
|
||||||
squirrel.Lt{"i.acquired_time": filter.AcquiredTime.Max},
|
squirrel.Lt{"i.acquired_time": filter.AcquiredTime.Max},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if filter.ScheduledDate != nil {
|
if filter.UnScheduled {
|
||||||
|
sq = sq.Where("i.scheduled_date IS NULL")
|
||||||
|
} else if filter.ScheduledDate != nil {
|
||||||
dateOr = append(dateOr, squirrel.And{
|
dateOr = append(dateOr, squirrel.And{
|
||||||
squirrel.GtOrEq{"i.scheduled_date": filter.ScheduledDate.Min.AsTime()},
|
squirrel.GtOrEq{"i.scheduled_date": filter.ScheduledDate.Min.AsTime()},
|
||||||
squirrel.Lt{"i.scheduled_date": filter.ScheduledDate.Max.AsTime()},
|
squirrel.Lt{"i.scheduled_date": filter.ScheduledDate.Max.AsTime()},
|
||||||
|
|||||||
@@ -41,13 +41,14 @@ type Project struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProjectRequirement struct {
|
type ProjectRequirement struct {
|
||||||
ID int
|
ID int
|
||||||
ScopeID int
|
ScopeID int
|
||||||
ProjectID int
|
ProjectID int
|
||||||
Name string
|
Name string
|
||||||
Status int
|
Status int
|
||||||
Description string
|
Description string
|
||||||
IsCoarse bool
|
IsCoarse bool
|
||||||
|
AggregateRequired int
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectRequirementStat struct {
|
type ProjectRequirementStat struct {
|
||||||
|
|||||||
@@ -156,17 +156,18 @@ func (q *Queries) InsertProject(ctx context.Context, arg InsertProjectParams) (s
|
|||||||
}
|
}
|
||||||
|
|
||||||
const insertProjectRequirement = `-- name: InsertProjectRequirement :execresult
|
const insertProjectRequirement = `-- name: InsertProjectRequirement :execresult
|
||||||
INSERT INTO project_requirement (scope_id, project_id, name, status, description, is_coarse)
|
INSERT INTO project_requirement (scope_id, project_id, name, status, description, is_coarse, aggregate_required)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
`
|
`
|
||||||
|
|
||||||
type InsertProjectRequirementParams struct {
|
type InsertProjectRequirementParams struct {
|
||||||
ScopeID int
|
ScopeID int
|
||||||
ProjectID int
|
ProjectID int
|
||||||
Name string
|
Name string
|
||||||
Status int
|
Status int
|
||||||
Description string
|
Description string
|
||||||
IsCoarse bool
|
IsCoarse bool
|
||||||
|
AggregateRequired int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) InsertProjectRequirement(ctx context.Context, arg InsertProjectRequirementParams) (sql.Result, error) {
|
func (q *Queries) InsertProjectRequirement(ctx context.Context, arg InsertProjectRequirementParams) (sql.Result, error) {
|
||||||
@@ -177,11 +178,12 @@ func (q *Queries) InsertProjectRequirement(ctx context.Context, arg InsertProjec
|
|||||||
arg.Status,
|
arg.Status,
|
||||||
arg.Description,
|
arg.Description,
|
||||||
arg.IsCoarse,
|
arg.IsCoarse,
|
||||||
|
arg.AggregateRequired,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const listProjectRequirements = `-- name: ListProjectRequirements :many
|
const listProjectRequirements = `-- name: ListProjectRequirements :many
|
||||||
SELECT id, scope_id, project_id, name, status, description, is_coarse FROM project_requirement WHERE project_id = ?
|
SELECT id, scope_id, project_id, name, status, description, is_coarse, aggregate_required FROM project_requirement WHERE project_id = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) ListProjectRequirements(ctx context.Context, projectID int) ([]ProjectRequirement, error) {
|
func (q *Queries) ListProjectRequirements(ctx context.Context, projectID int) ([]ProjectRequirement, error) {
|
||||||
@@ -201,6 +203,7 @@ func (q *Queries) ListProjectRequirements(ctx context.Context, projectID int) ([
|
|||||||
&i.Status,
|
&i.Status,
|
||||||
&i.Description,
|
&i.Description,
|
||||||
&i.IsCoarse,
|
&i.IsCoarse,
|
||||||
|
&i.AggregateRequired,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -330,17 +333,19 @@ UPDATE project_requirement
|
|||||||
SET name = ?,
|
SET name = ?,
|
||||||
status = ?,
|
status = ?,
|
||||||
description = ?,
|
description = ?,
|
||||||
is_coarse = ?
|
is_coarse = ?,
|
||||||
|
aggregate_required = ?
|
||||||
WHERE id = ? AND scope_id = ?
|
WHERE id = ? AND scope_id = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateProjectRequirementParams struct {
|
type UpdateProjectRequirementParams struct {
|
||||||
Name string
|
Name string
|
||||||
Status int
|
Status int
|
||||||
Description string
|
Description string
|
||||||
IsCoarse bool
|
IsCoarse bool
|
||||||
ID int
|
AggregateRequired int
|
||||||
ScopeID int
|
ID int
|
||||||
|
ScopeID int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateProjectRequirement(ctx context.Context, arg UpdateProjectRequirementParams) error {
|
func (q *Queries) UpdateProjectRequirement(ctx context.Context, arg UpdateProjectRequirementParams) error {
|
||||||
@@ -349,6 +354,7 @@ func (q *Queries) UpdateProjectRequirement(ctx context.Context, arg UpdateProjec
|
|||||||
arg.Status,
|
arg.Status,
|
||||||
arg.Description,
|
arg.Description,
|
||||||
arg.IsCoarse,
|
arg.IsCoarse,
|
||||||
|
arg.AggregateRequired,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
arg.ScopeID,
|
arg.ScopeID,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -191,37 +191,21 @@ func (q *Queries) ListSprintsAt(ctx context.Context, arg ListSprintsAtParams) ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
const listSprintsBetween = `-- name: ListSprintsBetween :many
|
const listSprintsBetween = `-- name: ListSprintsBetween :many
|
||||||
SELECT id, scope_id, name, description, from_time, to_time, is_timed, is_coarse, is_unweighted, kind, aggregate_name, aggregate_required FROM sprint WHERE scope_id = ?
|
SELECT id, scope_id, name, description, from_time, to_time, is_timed, is_coarse, is_unweighted, kind, aggregate_name, aggregate_required FROM sprint
|
||||||
AND (
|
WHERE scope_id = ?
|
||||||
-- from time is within from..to
|
AND from_time < ?
|
||||||
(from_time <= ? AND to_time > ?)
|
AND to_time >= ?
|
||||||
-- or to time is within from..to
|
ORDER BY from_time, name
|
||||||
OR (from_time <= ? AND to_time > ?)
|
|
||||||
-- or from and to time are on each their side of from..to
|
|
||||||
OR (from_time >= ? AND to_time < ?)
|
|
||||||
)
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type ListSprintsBetweenParams struct {
|
type ListSprintsBetweenParams struct {
|
||||||
ScopeID int
|
ScopeID int
|
||||||
FromTime time.Time
|
ToTime time.Time
|
||||||
ToTime time.Time
|
FromTime time.Time
|
||||||
FromTime_2 time.Time
|
|
||||||
ToTime_2 time.Time
|
|
||||||
FromTime_3 time.Time
|
|
||||||
ToTime_3 time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ListSprintsBetween(ctx context.Context, arg ListSprintsBetweenParams) ([]Sprint, error) {
|
func (q *Queries) ListSprintsBetween(ctx context.Context, arg ListSprintsBetweenParams) ([]Sprint, error) {
|
||||||
rows, err := q.query(ctx, q.listSprintsBetweenStmt, listSprintsBetween,
|
rows, err := q.query(ctx, q.listSprintsBetweenStmt, listSprintsBetween, arg.ScopeID, arg.ToTime, arg.FromTime)
|
||||||
arg.ScopeID,
|
|
||||||
arg.FromTime,
|
|
||||||
arg.ToTime,
|
|
||||||
arg.FromTime_2,
|
|
||||||
arg.ToTime_2,
|
|
||||||
arg.FromTime_3,
|
|
||||||
arg.ToTime_3,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+85
-23
@@ -35,6 +35,61 @@ func (r *projectRepository) Find(ctx context.Context, scopeID, projectID int) (*
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *projectRepository) FetchProjects(ctx context.Context, scopeID int, ids ...int) ([]entities.Project, error) {
|
||||||
|
if len(ids) == 0 {
|
||||||
|
return []entities.Project{}, nil
|
||||||
|
} else if len(ids) == 1 && scopeID != -1 {
|
||||||
|
project, err := r.Find(ctx, scopeID, ids[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []entities.Project{*project}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sq := squirrel.Select("id,scope_id,owner_id,name,status,description,created_time").
|
||||||
|
From("project").
|
||||||
|
Where(squirrel.Eq{"id": ids})
|
||||||
|
if scopeID != -1 {
|
||||||
|
sq = sq.Where(squirrel.Eq{"scope_id": scopeID})
|
||||||
|
}
|
||||||
|
|
||||||
|
query, args, err := sq.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
projects := make([]entities.Project, 0, len(ids))
|
||||||
|
for rows.Next() {
|
||||||
|
project := entities.Project{}
|
||||||
|
if err := rows.Scan(
|
||||||
|
&project.ID,
|
||||||
|
&project.ScopeID,
|
||||||
|
&project.OwnerID,
|
||||||
|
&project.Name,
|
||||||
|
&project.Status,
|
||||||
|
&project.Description,
|
||||||
|
&project.CreatedTime,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
projects = append(projects, project)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *projectRepository) List(ctx context.Context, scopeID int) ([]entities.Project, error) {
|
func (r *projectRepository) List(ctx context.Context, scopeID int) ([]entities.Project, error) {
|
||||||
rows, err := r.q.ListProjects(ctx, scopeID)
|
rows, err := r.q.ListProjects(ctx, scopeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -139,11 +194,14 @@ func (r *projectRepository) FetchRequirements(ctx context.Context, scopeID int,
|
|||||||
return []entities.Requirement{}, []entities.RequirementStat{}, nil
|
return []entities.Requirement{}, []entities.RequirementStat{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
query, args, err := squirrel.Select("id, scope_id, project_id, name, status, description, is_coarse").
|
sq := squirrel.Select("id, scope_id, project_id, name, status, description, is_coarse, aggregate_required").
|
||||||
From("project_requirement").
|
From("project_requirement").
|
||||||
Where(squirrel.Eq{"scope_id": scopeID}).
|
Where(squirrel.Eq{"id": requirementIDs})
|
||||||
Where(squirrel.Eq{"id": requirementIDs}).
|
if scopeID != -1 {
|
||||||
ToSql()
|
sq = sq.Where(squirrel.Eq{"scope_id": scopeID})
|
||||||
|
}
|
||||||
|
|
||||||
|
query, args, err := sq.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -163,6 +221,7 @@ func (r *projectRepository) FetchRequirements(ctx context.Context, scopeID int,
|
|||||||
&requirement.Status,
|
&requirement.Status,
|
||||||
&requirement.Description,
|
&requirement.Description,
|
||||||
&requirement.IsCoarse,
|
&requirement.IsCoarse,
|
||||||
|
&requirement.AggregateRequired,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -213,13 +272,14 @@ func (r *projectRepository) ListRequirements(ctx context.Context, projectID int)
|
|||||||
requirements := make([]entities.Requirement, 0, len(reqRows))
|
requirements := make([]entities.Requirement, 0, len(reqRows))
|
||||||
for _, row := range reqRows {
|
for _, row := range reqRows {
|
||||||
requirements = append(requirements, entities.Requirement{
|
requirements = append(requirements, entities.Requirement{
|
||||||
ID: row.ID,
|
ID: row.ID,
|
||||||
ScopeID: row.ScopeID,
|
ScopeID: row.ScopeID,
|
||||||
ProjectID: row.ProjectID,
|
ProjectID: row.ProjectID,
|
||||||
Name: row.Name,
|
Name: row.Name,
|
||||||
Description: row.Description,
|
Description: row.Description,
|
||||||
IsCoarse: row.IsCoarse,
|
IsCoarse: row.IsCoarse,
|
||||||
Status: models.Status(row.Status),
|
AggregateRequired: row.AggregateRequired,
|
||||||
|
Status: models.Status(row.Status),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,12 +297,13 @@ func (r *projectRepository) ListRequirements(ctx context.Context, projectID int)
|
|||||||
|
|
||||||
func (r *projectRepository) CreateRequirement(ctx context.Context, requirement entities.Requirement) (*entities.Requirement, error) {
|
func (r *projectRepository) CreateRequirement(ctx context.Context, requirement entities.Requirement) (*entities.Requirement, error) {
|
||||||
res, err := r.q.InsertProjectRequirement(ctx, mysqlcore.InsertProjectRequirementParams{
|
res, err := r.q.InsertProjectRequirement(ctx, mysqlcore.InsertProjectRequirementParams{
|
||||||
ScopeID: requirement.ScopeID,
|
ScopeID: requirement.ScopeID,
|
||||||
ProjectID: requirement.ProjectID,
|
ProjectID: requirement.ProjectID,
|
||||||
Name: requirement.Name,
|
Name: requirement.Name,
|
||||||
Status: int(requirement.Status),
|
Status: int(requirement.Status),
|
||||||
Description: requirement.Description,
|
Description: requirement.Description,
|
||||||
IsCoarse: requirement.IsCoarse,
|
IsCoarse: requirement.IsCoarse,
|
||||||
|
AggregateRequired: requirement.AggregateRequired,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -261,12 +322,13 @@ func (r *projectRepository) UpdateRequirement(ctx context.Context, requirement e
|
|||||||
requirement.Update(update)
|
requirement.Update(update)
|
||||||
|
|
||||||
return r.q.UpdateProjectRequirement(ctx, mysqlcore.UpdateProjectRequirementParams{
|
return r.q.UpdateProjectRequirement(ctx, mysqlcore.UpdateProjectRequirementParams{
|
||||||
Name: requirement.Name,
|
Name: requirement.Name,
|
||||||
Status: int(requirement.Status),
|
Status: int(requirement.Status),
|
||||||
Description: requirement.Description,
|
Description: requirement.Description,
|
||||||
IsCoarse: requirement.IsCoarse,
|
IsCoarse: requirement.IsCoarse,
|
||||||
ID: requirement.ID,
|
ID: requirement.ID,
|
||||||
ScopeID: requirement.ScopeID,
|
ScopeID: requirement.ScopeID,
|
||||||
|
AggregateRequired: requirement.AggregateRequired,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,15 +23,16 @@ DELETE FROM project WHERE id = ? AND scope_id = ?;
|
|||||||
SELECT * FROM project_requirement WHERE project_id = ?;
|
SELECT * FROM project_requirement WHERE project_id = ?;
|
||||||
|
|
||||||
-- name: InsertProjectRequirement :execresult
|
-- name: InsertProjectRequirement :execresult
|
||||||
INSERT INTO project_requirement (scope_id, project_id, name, status, description, is_coarse)
|
INSERT INTO project_requirement (scope_id, project_id, name, status, description, is_coarse, aggregate_required)
|
||||||
VALUES (?, ?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?, ?);
|
||||||
|
|
||||||
-- name: UpdateProjectRequirement :exec
|
-- name: UpdateProjectRequirement :exec
|
||||||
UPDATE project_requirement
|
UPDATE project_requirement
|
||||||
SET name = ?,
|
SET name = ?,
|
||||||
status = ?,
|
status = ?,
|
||||||
description = ?,
|
description = ?,
|
||||||
is_coarse = ?
|
is_coarse = ?,
|
||||||
|
aggregate_required = ?
|
||||||
WHERE id = ? AND scope_id = ?;
|
WHERE id = ? AND scope_id = ?;
|
||||||
|
|
||||||
-- name: DeleteProjectRequirement :exec
|
-- name: DeleteProjectRequirement :exec
|
||||||
|
|||||||
@@ -2,16 +2,11 @@
|
|||||||
SELECT * FROM sprint WHERE scope_id = ? AND from_time <= sqlc.arg(time) AND to_time > sqlc.arg(time);
|
SELECT * FROM sprint WHERE scope_id = ? AND from_time <= sqlc.arg(time) AND to_time > sqlc.arg(time);
|
||||||
|
|
||||||
-- name: ListSprintsBetween :many
|
-- name: ListSprintsBetween :many
|
||||||
SELECT * FROM sprint WHERE scope_id = ?
|
SELECT * FROM sprint
|
||||||
AND (
|
WHERE scope_id = sqlc.arg(scope_id)
|
||||||
-- from time is within from..to
|
AND from_time < sqlc.arg(to_time)
|
||||||
(from_time <= ? AND to_time > ?)
|
AND to_time >= sqlc.arg(from_time)
|
||||||
-- or to time is within from..to
|
ORDER BY from_time, name;
|
||||||
OR (from_time <= ? AND to_time > ?)
|
|
||||||
-- or from and to time are on each their side of from..to
|
|
||||||
OR (from_time >= ? AND to_time < ?)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
-- name: GetSprint :one
|
-- name: GetSprint :one
|
||||||
SELECT * FROM sprint WHERE id = ? AND scope_id = ?;
|
SELECT * FROM sprint WHERE id = ? AND scope_id = ?;
|
||||||
|
|||||||
@@ -75,13 +75,9 @@ func (r *sprintRepository) ListAt(ctx context.Context, scopeID int, at time.Time
|
|||||||
|
|
||||||
func (r *sprintRepository) ListBetween(ctx context.Context, scopeID int, from, to time.Time) ([]entities.Sprint, error) {
|
func (r *sprintRepository) ListBetween(ctx context.Context, scopeID int, from, to time.Time) ([]entities.Sprint, error) {
|
||||||
rows, err := r.q.ListSprintsBetween(ctx, mysqlcore.ListSprintsBetweenParams{
|
rows, err := r.q.ListSprintsBetween(ctx, mysqlcore.ListSprintsBetweenParams{
|
||||||
ScopeID: scopeID,
|
ScopeID: scopeID,
|
||||||
FromTime: from,
|
FromTime: from,
|
||||||
ToTime: from,
|
ToTime: to,
|
||||||
FromTime_2: to,
|
|
||||||
ToTime_2: to,
|
|
||||||
FromTime_3: from,
|
|
||||||
ToTime_3: to,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ func (r *statsRepository) List(ctx context.Context, scopeIDs ...int) ([]entities
|
|||||||
Select("id, scope_id, name, description, weight, allowed_amounts, is_primary").
|
Select("id, scope_id, name, description, weight, allowed_amounts, is_primary").
|
||||||
From("stat").
|
From("stat").
|
||||||
Where(squirrel.Eq{"scope_id": scopeIDs}).
|
Where(squirrel.Eq{"scope_id": scopeIDs}).
|
||||||
|
OrderBy("is_primary DESC", "name").
|
||||||
RunWith(r.db).
|
RunWith(r.db).
|
||||||
Query()
|
Query()
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE item CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE project CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE project_requirement CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE scope CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE sprint CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE stat CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
SELECT 'down SQL query';
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE project_requirement ADD COLUMN aggregate_required INT NOT NULL DEFAULT 0;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
ALTER TABLE project_requirement DROP COLUMN aggregate_required;
|
||||||
|
-- +goose StatementEnd
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package items
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.aiterp.net/stufflog3/stufflog3/internal/genutils"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
Label string `json:"label,omitempty"`
|
||||||
|
List []GroupReference `json:"list,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupOptions struct {
|
||||||
|
HideCreated bool `json:"hideCreated,omitempty"`
|
||||||
|
HideScheduled bool `json:"hideScheduled,omitempty"`
|
||||||
|
HideAcquired bool `json:"hideAcquired,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GroupReference struct {
|
||||||
|
Index int `json:"idx"`
|
||||||
|
Event string `json:"event"`
|
||||||
|
Time string `json:"time"`
|
||||||
|
|
||||||
|
sortKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateGroups(items []Result, opts GroupOptions) []Group {
|
||||||
|
groups := make(map[string]Group, 16)
|
||||||
|
|
||||||
|
// TODO: Get the scope from somewhere else, if necessary.
|
||||||
|
|
||||||
|
for i, item := range items {
|
||||||
|
if !opts.HideCreated && (item.AcquiredTime == nil || item.AcquiredTime.After(item.CreatedTime)) {
|
||||||
|
addItem(groups, i, "created", item.CreatedTime, tz)
|
||||||
|
}
|
||||||
|
if !opts.HideScheduled && item.ScheduledDate != nil {
|
||||||
|
addItem(groups, i, "scheduled", item.ScheduledDate.WithTimeAndLocation(23, 59, 59, tz), tz)
|
||||||
|
}
|
||||||
|
if !opts.HideAcquired && item.AcquiredTime != nil {
|
||||||
|
addItem(groups, i, "acquired", *item.AcquiredTime, tz)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
sort.Slice(group.List, func(i, j int) bool {
|
||||||
|
return group.List[j].sortKey < group.List[i].sortKey
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
groupSlice := genutils.MapValues(groups)
|
||||||
|
sort.Slice(groupSlice, func(i, j int) bool {
|
||||||
|
return groupSlice[j].Label < groupSlice[i].Label
|
||||||
|
})
|
||||||
|
|
||||||
|
return groupSlice
|
||||||
|
}
|
||||||
|
|
||||||
|
func addItem(groups map[string]Group, index int, event string, eventDate time.Time, tz *time.Location) {
|
||||||
|
groupDate := eventDate.In(tz).Add(-time.Minute * 300)
|
||||||
|
eventDate = eventDate.In(tz)
|
||||||
|
|
||||||
|
groupKey := groupDate.Format("2006-01-02")
|
||||||
|
var sortKey string
|
||||||
|
if event == "scheduled" {
|
||||||
|
sortKey = eventDate.Format("2006-01-02") + " 23:59:59.999"
|
||||||
|
} else {
|
||||||
|
sortKey = eventDate.Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Why, Go, must you be so.
|
||||||
|
group, ok := groups[groupKey]
|
||||||
|
if !ok {
|
||||||
|
group = Group{
|
||||||
|
Label: groupDate.Format("2006-01-02 (Monday)"),
|
||||||
|
List: make([]GroupReference, 0, 16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ref := range group.List {
|
||||||
|
if ref.Index == index {
|
||||||
|
group.List = append(group.List[:i], group.List[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group.List = append(group.List, GroupReference{
|
||||||
|
Index: index,
|
||||||
|
Event: event,
|
||||||
|
Time: eventDate.Format("15:04"),
|
||||||
|
sortKey: sortKey,
|
||||||
|
})
|
||||||
|
groups[groupKey] = group
|
||||||
|
}
|
||||||
|
|
||||||
|
var tz *time.Location
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
tz, err = time.LoadLocation("Europe/Oslo")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,10 @@ type Repository interface {
|
|||||||
UpdateStat(ctx context.Context, progress entities.ItemStat) error
|
UpdateStat(ctx context.Context, progress entities.ItemStat) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProjectFetcher interface {
|
||||||
|
FetchProjects(ctx context.Context, scopeID int, ids ...int) ([]entities.Project, error)
|
||||||
|
}
|
||||||
|
|
||||||
type RequirementFetcher interface {
|
type RequirementFetcher interface {
|
||||||
FetchRequirements(ctx context.Context, scopeID int, requirementIDs ...int) ([]entities.Requirement, []entities.RequirementStat, error)
|
FetchRequirements(ctx context.Context, scopeID int, requirementIDs ...int) ([]entities.Requirement, []entities.RequirementStat, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,34 @@ package items
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.aiterp.net/stufflog3/stufflog3/entities"
|
"git.aiterp.net/stufflog3/stufflog3/entities"
|
||||||
|
"git.aiterp.net/stufflog3/stufflog3/models"
|
||||||
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
|
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
entities.Item
|
entities.Item
|
||||||
|
|
||||||
Requirement *entities.Requirement `json:"requirement,omitempty"`
|
Requirement *ResultRequirement `json:"requirement,omitempty"`
|
||||||
Stats []ResultStat `json:"stats"`
|
Project *ResultProject `json:"project,omitempty"`
|
||||||
|
Stats []ResultStat `json:"stats"`
|
||||||
|
WeightedAcquired float64 `json:"weightedAcquired"`
|
||||||
|
WeightedRequired float64 `json:"weightedRequired"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResultRequirement struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status models.Status `json:"status"`
|
||||||
|
StatusName string `json:"statusName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResultProject struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
CreatedTime time.Time `json:"createdTime"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status models.Status `json:"status"`
|
||||||
|
StatusName string `json:"statusName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) Stat(statID int) *ResultStat {
|
func (r *Result) Stat(statID int) *ResultStat {
|
||||||
@@ -53,6 +73,7 @@ func (r *Result) AddStat(scope scopes.Result, stat entities.ItemStat) {
|
|||||||
Required: stat.Required,
|
Required: stat.Required,
|
||||||
})
|
})
|
||||||
r.SortStats(scope)
|
r.SortStats(scope)
|
||||||
|
r.RecalculateWeighted()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Result) SortStats(scope scopes.Result) {
|
func (r *Result) SortStats(scope scopes.Result) {
|
||||||
@@ -76,6 +97,18 @@ func (r *Result) RemoveStat(id int) {
|
|||||||
r.Stats = append(r.Stats[:i], r.Stats[i+1:]...)
|
r.Stats = append(r.Stats[:i], r.Stats[i+1:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.RecalculateWeighted()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) RecalculateWeighted() {
|
||||||
|
r.WeightedAcquired = 0
|
||||||
|
r.WeightedRequired = 0
|
||||||
|
|
||||||
|
for _, stat := range r.Stats {
|
||||||
|
r.WeightedAcquired += float64(stat.Acquired) * stat.Weight
|
||||||
|
r.WeightedRequired += float64(stat.Required) * stat.Weight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResultStat struct {
|
type ResultStat struct {
|
||||||
@@ -86,7 +119,7 @@ type ResultStat struct {
|
|||||||
Required int `json:"required"`
|
Required int `json:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateResult(item entities.Item, scope scopes.Result, progresses []entities.ItemStat, requirements []entities.Requirement) Result {
|
func generateResult(item entities.Item, scope scopes.Result, progresses []entities.ItemStat, requirements []entities.Requirement, projects []entities.Project) Result {
|
||||||
res := Result{
|
res := Result{
|
||||||
Item: item,
|
Item: item,
|
||||||
Stats: make([]ResultStat, 0, 8),
|
Stats: make([]ResultStat, 0, 8),
|
||||||
@@ -95,7 +128,28 @@ func generateResult(item entities.Item, scope scopes.Result, progresses []entiti
|
|||||||
if item.RequirementID != nil {
|
if item.RequirementID != nil {
|
||||||
for _, req := range requirements {
|
for _, req := range requirements {
|
||||||
if req.ID == *item.RequirementID {
|
if req.ID == *item.RequirementID {
|
||||||
res.Requirement = &req
|
res.Requirement = &ResultRequirement{
|
||||||
|
ID: req.ID,
|
||||||
|
Name: req.Name,
|
||||||
|
Status: req.Status,
|
||||||
|
StatusName: scope.StatusName(req.Status),
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if item.ProjectID != nil {
|
||||||
|
for _, project := range projects {
|
||||||
|
if project.ID == *item.ProjectID {
|
||||||
|
res.Project = &ResultProject{
|
||||||
|
ID: project.ID,
|
||||||
|
CreatedTime: project.CreatedTime,
|
||||||
|
Name: project.Name,
|
||||||
|
Status: project.Status,
|
||||||
|
StatusName: scope.StatusName(project.Status),
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,6 +175,7 @@ func generateResult(item entities.Item, scope scopes.Result, progresses []entiti
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.SortStats(scope)
|
res.SortStats(scope)
|
||||||
|
res.RecalculateWeighted()
|
||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user