From 6d7c82c3a87669150da1311ca8b4bfd545206412 Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Sat, 22 Feb 2025 18:40:49 -0500 Subject: [PATCH 1/3] Talker alias filter backend --- pkg/calls/callstore/store.go | 35 ++++++++++--------- pkg/database/calls.sql.go | 64 ++++++++++++++++++++-------------- sql/postgres/queries/calls.sql | 6 ++++ 3 files changed, 62 insertions(+), 43 deletions(-) diff --git a/pkg/calls/callstore/store.go b/pkg/calls/callstore/store.go index e405ec2..4df939b 100644 --- a/pkg/calls/callstore/store.go +++ b/pkg/calls/callstore/store.go @@ -203,6 +203,7 @@ type CallsParams struct { TagsAny []string `json:"tagsAny"` TagsNot []string `json:"tagsNot"` TGFilter *string `json:"tgFilter"` + SourceFilter *string `json:"sourceFilter"` AtLeastSeconds *float32 `json:"atLeastSeconds"` UnknownTG bool `json:"unknownTG"` } @@ -217,15 +218,16 @@ func (s *postgresStore) Calls(ctx context.Context, p CallsParams) (rows []databa offset, perPage := p.Pagination.OffsetPerPage(100) par := database.ListCallsPParams{ - Start: p.Start.PGTypeTSTZ(), - End: p.End.PGTypeTSTZ(), - TagsAny: p.TagsAny, - TagsNot: p.TagsNot, - Offset: offset, - PerPage: perPage, - Direction: p.Direction.DirString(common.DirAsc), - TGFilter: p.TGFilter, - UnknownTG: p.UnknownTG, + Start: p.Start.PGTypeTSTZ(), + End: p.End.PGTypeTSTZ(), + TagsAny: p.TagsAny, + TagsNot: p.TagsNot, + Offset: offset, + PerPage: perPage, + Direction: p.Direction.DirString(common.DirAsc), + TGFilter: p.TGFilter, + SourceFilter: p.SourceFilter, + UnknownTG: p.UnknownTG, } if p.AtLeastSeconds != nil { @@ -241,13 +243,14 @@ func (s *postgresStore) Calls(ctx context.Context, p CallsParams) (rows []databa txErr := db.InTx(ctx, func(db database.Store) error { var err error count, err = db.ListCallsCount(ctx, database.ListCallsCountParams{ - Start: par.Start, - End: par.End, - TagsAny: par.TagsAny, - TagsNot: par.TagsNot, - TGFilter: par.TGFilter, - LongerThan: par.LongerThan, - UnknownTG: par.UnknownTG, + Start: par.Start, + End: par.End, + TagsAny: par.TagsAny, + TagsNot: par.TagsNot, + TGFilter: par.TGFilter, + SourceFilter: p.SourceFilter, + LongerThan: par.LongerThan, + UnknownTG: par.UnknownTG, }) if err != nil { return err diff --git a/pkg/database/calls.sql.go b/pkg/database/calls.sql.go index 120110f..45347be 100644 --- a/pkg/database/calls.sql.go +++ b/pkg/database/calls.sql.go @@ -317,22 +317,26 @@ CASE WHEN $4::TEXT[] IS NOT NULL THEN tgs.name ILIKE '%' || $5 || '%' OR tgs.alpha_tag ILIKE '%' || $5 || '%' ) ELSE TRUE END) AND -(CASE WHEN $6::NUMERIC IS NOT NULL THEN ( - c.duration > $6 +(CASE WHEN $6::TEXT IS NOT NULL THEN ( + c.talker_alias ILIKE '%' || $6 || '%' ) ELSE TRUE END) AND -(CASE WHEN $7::BOOLEAN = TRUE THEN ( +(CASE WHEN $7::NUMERIC IS NOT NULL THEN ( + c.duration > $7 + ) ELSE TRUE END) AND +(CASE WHEN $8::BOOLEAN = TRUE THEN ( tgs.tgid IS NULL ) ELSE TRUE END) ` type ListCallsCountParams struct { - Start pgtype.Timestamptz `json:"start"` - End pgtype.Timestamptz `json:"end"` - TagsAny []string `json:"tagsAny"` - TagsNot []string `json:"tagsNot"` - TGFilter *string `json:"tgFilter"` - LongerThan pgtype.Numeric `json:"longerThan"` - UnknownTG bool `json:"unknownTg"` + Start pgtype.Timestamptz `json:"start"` + End pgtype.Timestamptz `json:"end"` + TagsAny []string `json:"tagsAny"` + TagsNot []string `json:"tagsNot"` + TGFilter *string `json:"tgFilter"` + SourceFilter *string `json:"sourceFilter"` + LongerThan pgtype.Numeric `json:"longerThan"` + UnknownTG bool `json:"unknownTg"` } func (q *Queries) ListCallsCount(ctx context.Context, arg ListCallsCountParams) (int64, error) { @@ -342,6 +346,7 @@ func (q *Queries) ListCallsCount(ctx context.Context, arg ListCallsCountParams) arg.TagsAny, arg.TagsNot, arg.TGFilter, + arg.SourceFilter, arg.LongerThan, arg.UnknownTG, ) @@ -377,31 +382,35 @@ CASE WHEN $4::TEXT[] IS NOT NULL THEN tgs.name ILIKE '%' || $5 || '%' OR tgs.alpha_tag ILIKE '%' || $5 || '%' ) ELSE TRUE END) AND -(CASE WHEN $6::NUMERIC IS NOT NULL THEN ( - c.duration > $6 +(CASE WHEN $6::TEXT IS NOT NULL THEN ( + c.talker_alias ILIKE '%' || $6 || '%' ) ELSE TRUE END) AND -(CASE WHEN $7::BOOLEAN = TRUE THEN ( +(CASE WHEN $7::NUMERIC IS NOT NULL THEN ( + c.duration > $7 + ) ELSE TRUE END) AND +(CASE WHEN $8::BOOLEAN = TRUE THEN ( tgs.tgid IS NULL ) ELSE TRUE END) GROUP BY c.id, c.call_date ORDER BY -CASE WHEN $8::TEXT = 'asc' THEN c.call_date END ASC, -CASE WHEN $8 = 'desc' THEN c.call_date END DESC -OFFSET $9 ROWS -FETCH NEXT $10 ROWS ONLY +CASE WHEN $9::TEXT = 'asc' THEN c.call_date END ASC, +CASE WHEN $9 = 'desc' THEN c.call_date END DESC +OFFSET $10 ROWS +FETCH NEXT $11 ROWS ONLY ` type ListCallsPParams struct { - Start pgtype.Timestamptz `json:"start"` - End pgtype.Timestamptz `json:"end"` - TagsAny []string `json:"tagsAny"` - TagsNot []string `json:"tagsNot"` - TGFilter *string `json:"tgFilter"` - LongerThan pgtype.Numeric `json:"longerThan"` - UnknownTG bool `json:"unknownTg"` - Direction string `json:"direction"` - Offset int32 `json:"offset"` - PerPage int32 `json:"perPage"` + Start pgtype.Timestamptz `json:"start"` + End pgtype.Timestamptz `json:"end"` + TagsAny []string `json:"tagsAny"` + TagsNot []string `json:"tagsNot"` + TGFilter *string `json:"tgFilter"` + SourceFilter *string `json:"sourceFilter"` + LongerThan pgtype.Numeric `json:"longerThan"` + UnknownTG bool `json:"unknownTg"` + Direction string `json:"direction"` + Offset int32 `json:"offset"` + PerPage int32 `json:"perPage"` } type ListCallsPRow struct { @@ -421,6 +430,7 @@ func (q *Queries) ListCallsP(ctx context.Context, arg ListCallsPParams) ([]ListC arg.TagsAny, arg.TagsNot, arg.TGFilter, + arg.SourceFilter, arg.LongerThan, arg.UnknownTG, arg.Direction, diff --git a/sql/postgres/queries/calls.sql b/sql/postgres/queries/calls.sql index 13798c4..a4083b2 100644 --- a/sql/postgres/queries/calls.sql +++ b/sql/postgres/queries/calls.sql @@ -125,6 +125,9 @@ CASE WHEN sqlc.narg('tags_not')::TEXT[] IS NOT NULL THEN tgs.name ILIKE '%' || @tg_filter || '%' OR tgs.alpha_tag ILIKE '%' || @tg_filter || '%' ) ELSE TRUE END) AND +(CASE WHEN sqlc.narg('source_filter')::TEXT IS NOT NULL THEN ( + c.talker_alias ILIKE '%' || @source_filter || '%' + ) ELSE TRUE END) AND (CASE WHEN sqlc.narg('longer_than')::NUMERIC IS NOT NULL THEN ( c.duration > @longer_than ) ELSE TRUE END) AND @@ -158,6 +161,9 @@ CASE WHEN sqlc.narg('tags_not')::TEXT[] IS NOT NULL THEN tgs.name ILIKE '%' || @tg_filter || '%' OR tgs.alpha_tag ILIKE '%' || @tg_filter || '%' ) ELSE TRUE END) AND +(CASE WHEN sqlc.narg('source_filter')::TEXT IS NOT NULL THEN ( + c.talker_alias ILIKE '%' || @source_filter || '%' + ) ELSE TRUE END) AND (CASE WHEN sqlc.narg('longer_than')::NUMERIC IS NOT NULL THEN ( c.duration > @longer_than ) ELSE TRUE END) AND From df180cf42ce99fcfb47ed9bfc8c4f9e4aa98427a Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Sat, 22 Feb 2025 19:20:23 -0500 Subject: [PATCH 2/3] Filter source UI --- .../src/app/calls/calls.component.html | 35 +++++++++++++++++-- .../src/app/calls/calls.component.scss | 7 ++-- .../stillbox/src/app/calls/calls.component.ts | 13 ++++++- .../stillbox/src/app/calls/calls.service.ts | 5 +-- .../incident/incident.component.html | 12 +++++++ .../incidents/incident/incident.component.ts | 3 ++ 6 files changed, 67 insertions(+), 8 deletions(-) diff --git a/client/stillbox/src/app/calls/calls.component.html b/client/stillbox/src/app/calls/calls.component.html index c7c6105..5121b80 100644 --- a/client/stillbox/src/app/calls/calls.component.html +++ b/client/stillbox/src/app/calls/calls.component.html @@ -49,6 +49,25 @@ close + + Source Filter + + + Any Tags {{ tgAlpha }} Source - + + @let tlkAlias = call | talker; + @if (tlkAlias) { + {{ tlkAlias }} + } @else { + — + } + Duration diff --git a/client/stillbox/src/app/calls/calls.component.scss b/client/stillbox/src/app/calls/calls.component.scss index 7d7bbd3..ce28dfe 100644 --- a/client/stillbox/src/app/calls/calls.component.scss +++ b/client/stillbox/src/app/calls/calls.component.scss @@ -86,7 +86,7 @@ form { } .filterBox { - flex: 1 1 300px; + flex: 1 1 200px; } .durationFilter { @@ -94,13 +94,14 @@ form { } .tagSelect { - flex: 1 1 250px; + flex: 1 1 220px; } .in-incident { background-color: rgb(59, 0, 59); } -a.tgFilter:hover { +a.tgFilter:hover, +a.srcFilter:hover { text-decoration: underline; } diff --git a/client/stillbox/src/app/calls/calls.component.ts b/client/stillbox/src/app/calls/calls.component.ts index 19a8ab5..f3df31d 100644 --- a/client/stillbox/src/app/calls/calls.component.ts +++ b/client/stillbox/src/app/calls/calls.component.ts @@ -112,6 +112,7 @@ export class CallsComponent { start: new FormControl(this.lTime(new Date())), end: new FormControl(null), filter: new FormControl(''), + sourceFilter: new FormControl(''), duration: new FormControl(0), tagsAny: new FormControl([]), tagsNot: new FormControl([]), @@ -139,12 +140,18 @@ export class CallsComponent { return numSelected === numRows; } - searchFilter(filt: string | null) { + searchTGFilter(filt: string | null) { if (filt) { this.form.controls['filter'].setValue(filt); } } + searchSrcFilter(filt: string | null) { + if (filt) { + this.form.controls['sourceFilter'].setValue(filt); + } + } + buildParams(p: PageEvent, serverPage: number): CallsListParams { const par: CallsListParams = { start: new Date(this.form.controls['start'].value!), @@ -167,6 +174,10 @@ export class CallsComponent { this.form.controls['filter'].value != '' ? this.form.controls['filter'].value : null, + sourceFilter: + this.form.controls['sourceFilter'].value != '' + ? this.form.controls['sourceFilter'].value + : null, atLeastSeconds: this.form.controls['duration'].value != null && this.form.controls['duration'].value > 0 diff --git a/client/stillbox/src/app/calls/calls.service.ts b/client/stillbox/src/app/calls/calls.service.ts index a90ed08..61d2598 100644 --- a/client/stillbox/src/app/calls/calls.service.ts +++ b/client/stillbox/src/app/calls/calls.service.ts @@ -33,12 +33,12 @@ export class DatePipe implements PipeTransform { pure: true, }) export class TalkerPipe implements PipeTransform { - transform(call: CallRecord, args?: any): string { + transform(call: CallRecord, args?: any): string | null { if (call.talkerAlias != null) { return call.talkerAlias; } - return '—'; + return null; } } @@ -175,6 +175,7 @@ export interface CallsListParams { page: number; perPage: number; tgFilter: string | null; + sourceFilter: string | null; atLeastSeconds: number | null; } diff --git a/client/stillbox/src/app/incidents/incident/incident.component.html b/client/stillbox/src/app/incidents/incident/incident.component.html index 3e18495..9717ff0 100644 --- a/client/stillbox/src/app/incidents/incident/incident.component.html +++ b/client/stillbox/src/app/incidents/incident/incident.component.html @@ -102,6 +102,18 @@ {{ call | talkgroup: "alpha" | async }} + + Source + + @let tlkAlias = call | talker; + @if (tlkAlias) { + {{ tlkAlias }} + } @else { + — + } + + + Duration diff --git a/client/stillbox/src/app/incidents/incident/incident.component.ts b/client/stillbox/src/app/incidents/incident/incident.component.ts index a868c8a..84d6603 100644 --- a/client/stillbox/src/app/incidents/incident/incident.component.ts +++ b/client/stillbox/src/app/incidents/incident/incident.component.ts @@ -35,6 +35,7 @@ import { TimePipe, DatePipe, DownloadURLPipe, + TalkerPipe, } from '../../calls/calls.service'; import { CallPlayerComponent } from '../../calls/player/call-player/call-player.component'; import { FmtDatePipe } from '../incidents.component'; @@ -141,6 +142,7 @@ export class IncidentEditDialogComponent { MatIconModule, MatCardModule, FixedPointPipe, + TalkerPipe, TimePipe, DatePipe, TalkgroupPipe, @@ -169,6 +171,7 @@ export class IncidentComponent { 'system', 'group', 'talkgroup', + 'talker', 'duration', ]; callsResult = new MatTableDataSource(); From 2e580d8b4ca2e7334fabdf0d0032c6f41d90d94c Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Sat, 22 Feb 2025 19:28:05 -0500 Subject: [PATCH 3/3] Don't wrap filter buttons --- client/stillbox/src/app/calls/calls.component.scss | 2 +- .../src/app/incidents/incident/incident.component.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/stillbox/src/app/calls/calls.component.scss b/client/stillbox/src/app/calls/calls.component.scss index ce28dfe..361029c 100644 --- a/client/stillbox/src/app/calls/calls.component.scss +++ b/client/stillbox/src/app/calls/calls.component.scss @@ -86,7 +86,7 @@ form { } .filterBox { - flex: 1 1 200px; + flex: 1 2 150px; } .durationFilter { diff --git a/client/stillbox/src/app/incidents/incident/incident.component.html b/client/stillbox/src/app/incidents/incident/incident.component.html index 9717ff0..60cd814 100644 --- a/client/stillbox/src/app/incidents/incident/incident.component.html +++ b/client/stillbox/src/app/incidents/incident/incident.component.html @@ -102,12 +102,12 @@ {{ call | talkgroup: "alpha" | async }} - + Source @let tlkAlias = call | talker; @if (tlkAlias) { - {{ tlkAlias }} + {{ tlkAlias }} } @else { — }