add log filters for project group, project and item.
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -131,7 +131,7 @@ export class StufflogClient {
|
||||
return data.log;
|
||||
}
|
||||
|
||||
async listLogs({minTime, maxTime}: LogFilter): Promise<LogResult[]> {
|
||||
async listLogs({minTime, maxTime, itemIds, projectIds, projectGroupIds}: LogFilter): Promise<LogResult[]> {
|
||||
let queries = [];
|
||||
if (minTime != null) {
|
||||
queries.push(`minTime=${minTime.toISOString()}`);
|
||||
@@ -139,6 +139,15 @@ export class StufflogClient {
|
||||
if (maxTime != null) {
|
||||
queries.push(`maxTime=${maxTime.toISOString()}`);
|
||||
}
|
||||
if (itemIds != null) {
|
||||
queries.push(`items=${itemIds.join(",")}`)
|
||||
}
|
||||
if (projectIds != null) {
|
||||
queries.push(`projects=${projectIds.join(",")}`)
|
||||
}
|
||||
if (projectGroupIds != null) {
|
||||
queries.push(`groups=${projectGroupIds.join(",")}`)
|
||||
}
|
||||
|
||||
const query = queries.length > 0 ? `?${queries.join("&")}` : "";
|
||||
|
||||
|
||||
@@ -65,14 +65,19 @@ div.styled > div:first {
|
||||
}
|
||||
|
||||
div.styled > div {
|
||||
padding: 0.5em;
|
||||
padding: 0 0.5em;
|
||||
margin: 0 auto;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
div.styled label {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
div.styled select, div.styled input {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5em;
|
||||
padding: 0.35em;
|
||||
|
||||
background: #222;
|
||||
color: #777;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
export let name = "";
|
||||
export let disabled = false;
|
||||
export let optional = false;
|
||||
export let enableWholeGroup = false;
|
||||
|
||||
$: {
|
||||
if ($groupStore.stale && !$groupStore.loading) {
|
||||
@@ -28,6 +29,9 @@
|
||||
{/if}
|
||||
{#each $groupStore.groups as group (group.id)}
|
||||
<optgroup label={group.name}>
|
||||
{#if enableWholeGroup}
|
||||
<option value={group.items.map(it => it.id).join(",")}>All "{group.name}" items</option>
|
||||
{/if}
|
||||
{#each group.items as item (item.id)}
|
||||
<option value={item.id} selected={item.id === value}>{item.name} ({item.groupWeight})</option>
|
||||
{/each}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<select name={name} bind:value={value} disabled={disabled || $projectGroupStore.loading}>
|
||||
{#if optional}
|
||||
<option value={""} selected={"" === value}>None</option>
|
||||
<option value={""} selected={"" === value}>{$projectGroupStore.loading ? "Loading..." : "None"}</option>
|
||||
{/if}
|
||||
{#each $projectGroupStore.groups as group (group.id)}
|
||||
{#if group.id !== "META_UNGROUPED"}
|
||||
|
||||
@@ -12,6 +12,7 @@ import projectGroupStore from "../stores/projectGroup";
|
||||
export let groupId = "";
|
||||
export let disabled = false;
|
||||
export let optional = false;
|
||||
export let forceGroup = false;
|
||||
|
||||
let optGroups: OptGroup[]
|
||||
|
||||
@@ -23,7 +24,13 @@ import projectGroupStore from "../stores/projectGroup";
|
||||
if (group != null) {
|
||||
projects = group.projects;
|
||||
labels = group.categoryNames;
|
||||
|
||||
if (forceGroup && !group.projects.find(p => p.id === value)) {
|
||||
value = "";
|
||||
}
|
||||
}
|
||||
} else if (forceGroup) {
|
||||
value = "";
|
||||
}
|
||||
|
||||
optGroups = [
|
||||
@@ -78,7 +85,7 @@ import projectGroupStore from "../stores/projectGroup";
|
||||
|
||||
<select name={name} bind:value={value} disabled={disabled || $projectStore.loading}>
|
||||
{#if optional}
|
||||
<option value={""} selected={"" === value}>None</option>
|
||||
<option value={""} selected={"" === value}>{$projectStore.loading ? "Loading..." : "None"}</option>
|
||||
{/if}
|
||||
{#each optGroups as group (group.status)}
|
||||
{#if group.projects.length > 0}
|
||||
|
||||
@@ -15,6 +15,9 @@ export default interface Log {
|
||||
export interface LogFilter {
|
||||
minTime?: Date
|
||||
maxTime?: Date
|
||||
itemIds?: string[]
|
||||
projectIds?: string[]
|
||||
projectGroupIds?: string[]
|
||||
}
|
||||
|
||||
export interface LogResult extends Log {
|
||||
|
||||
@@ -1,28 +1,52 @@
|
||||
<script lang="ts">
|
||||
import LogEntry from "../components/LogEntry.svelte";
|
||||
import type { LogResult } from "../models/log";
|
||||
import type { LogFilter, LogResult } from "../models/log";
|
||||
import logStore from "../stores/logs";
|
||||
import { addDays, endOfDay, endOfWeek, formatFormTime, formatWeekdayDate, startOfDay, startOfWeek } from "../utils/time";
|
||||
import { addDays, formatWeekdayDate, startOfDay } from "../utils/time";
|
||||
import { allOffsets, RelativeDateRange } from "../utils/date-range";
|
||||
import EmptyList from "../components/EmptyList.svelte";
|
||||
import RefreshSelection from "../components/RefreshSelection.svelte";
|
||||
import DateRangeSelect from "../components/DateRangeSelect.svelte";
|
||||
import EveryMinute from "../components/EveryMinute.svelte";
|
||||
import { allOffsets, RelativeDateRange } from "../utils/date-range";
|
||||
|
||||
import ProjectGroupSelect from "../components/ProjectGroupSelect.svelte";
|
||||
import ProjectSelect from "../components/ProjectSelect.svelte";
|
||||
import ItemSelect from "../components/ItemSelect.svelte";
|
||||
import projectStore from "../stores/project";
|
||||
import projectGroupStore from "../stores/projectGroup";
|
||||
|
||||
const lsValue = localStorage.getItem("sl2.logspage.range")
|
||||
|
||||
let groupedLogs: {day: Date, text: string, logs: LogResult[]}[] = [];
|
||||
let range = allOffsets.find(o => o.name === lsValue) || new RelativeDateRange(-7, "day");
|
||||
let now = new Date();
|
||||
let error = "";
|
||||
let emptyMessage = `No logs since ${formatWeekdayDate(range.calculate(now)[0])}.`;
|
||||
let emptyMessage = "Loading...";
|
||||
|
||||
let min: Date;
|
||||
let max: Date;
|
||||
|
||||
let projectGroupId = localStorage.getItem("sl2.logspage.project_group_id");
|
||||
let projectId = localStorage.getItem("sl2.logspage.project_id");
|
||||
let itemId = localStorage.getItem("sl2.logspage.item_id");
|
||||
|
||||
$: [min, max] = range.calculate(now);
|
||||
|
||||
$: localStorage.setItem("sl2.logspage.range", range.name);
|
||||
$: localStorage.setItem("sl2.logspage.project_group_id", projectGroupId);
|
||||
$: localStorage.setItem("sl2.logspage.project_id", projectId);
|
||||
$: localStorage.setItem("sl2.logspage.item_id", itemId);
|
||||
|
||||
$: {
|
||||
if ($projectStore.stale && !$projectStore.loading) {
|
||||
projectStore.load({});
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($projectGroupStore.stale && !$projectGroupStore.loading) {
|
||||
projectGroupStore.load();
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
error = "";
|
||||
@@ -30,11 +54,16 @@
|
||||
if (!Number.isNaN(min.getTime()) && !Number.isNaN(max.getTime())) {
|
||||
if (min.getTime() < max.getTime()) {
|
||||
if (!$logStore.loading) {
|
||||
if ($logStore.stale || $logStore.filter.minTime?.getTime() != min.getTime() || $logStore.filter.maxTime?.getTime() != max.getTime()) {
|
||||
logStore.load({
|
||||
minTime: min,
|
||||
maxTime: max,
|
||||
});
|
||||
const filter: LogFilter = {
|
||||
minTime: min,
|
||||
maxTime: max,
|
||||
itemIds: itemId ? itemId.split(",") : void(0),
|
||||
projectIds: projectId ? [projectId] : void(0),
|
||||
projectGroupIds: (!projectId && projectGroupId) ? [projectGroupId] : void(0),
|
||||
}
|
||||
|
||||
if ($logStore.stale || JSON.stringify(filter) !== JSON.stringify($logStore.filter)) {
|
||||
logStore.load(filter);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -47,7 +76,9 @@
|
||||
|
||||
$: {
|
||||
if (!$logStore.loading && !$logStore.stale) {
|
||||
emptyMessage = `No logs between ${formatWeekdayDate(min)} and ${formatWeekdayDate(max)}.`;
|
||||
emptyMessage = `No matching logs between ${formatWeekdayDate(min)} and ${formatWeekdayDate(max)}.`;
|
||||
} else {
|
||||
emptyMessage = "Loading...";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +132,20 @@
|
||||
|
||||
<div class="page">
|
||||
<DateRangeSelect label="Time Filter" noFuture styled bind:value={range} />
|
||||
<div class="extra-filters">
|
||||
<div class="filter-group">
|
||||
<label for="projectGroup">Project Group</label>
|
||||
<ProjectGroupSelect name="projectGroup" optional bind:value={projectGroupId} />
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="project">Project</label>
|
||||
<ProjectSelect name="project" disabled={projectGroupId === ""} optional bind:value={projectId} groupId={projectGroupId} forceGroup />
|
||||
</div>
|
||||
<div class="filter-group">
|
||||
<label for="item">Item</label>
|
||||
<ItemSelect name="item" optional bind:value={itemId} enableWholeGroup />
|
||||
</div>
|
||||
</div>
|
||||
<div class="error">{error}</div>
|
||||
<div class="log-list" class:loading={$logStore.loading}>
|
||||
{#each groupedLogs as logGroup (logGroup.day.getTime())}
|
||||
@@ -139,6 +184,35 @@
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
div.extra-filters {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
div.filter-group {
|
||||
flex: 1;
|
||||
padding: 0 0.5em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
div.filter-group label {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
div.filter-group :global(select) {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
background: #222;
|
||||
color: #777;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: vertical;
|
||||
}
|
||||
div.filter-group :global(select:focus) {
|
||||
background: #191919;
|
||||
color: #CCC;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
font-weight: 100;
|
||||
@@ -146,4 +220,15 @@
|
||||
margin: 0;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
div.extra-filters {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
div.filter-group {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user