Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Activity Feed's User and Organization filters search all users/organizations #1149

Merged
merged 2 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,39 +114,10 @@
filterTitle;
context: { $implicit: 'Participating User', value: userId() }
"></ng-container>
<nz-select
nzPlaceHolder="Select User"
nzMode="multiple"
[ngModel]="userId()"
(ngModelChange)="userId.set($event)"
[nzCustomTemplate]="userLabel"
[nzOptionOverflowSize]="12"
[nzOptionHeightPx]="28">
<nz-option
*ngFor="let user of cvcFilterOptions().uniqueParticipants"
nzCustomContent
[nzLabel]="user.displayName"
[nzValue]="user.id">
<ng-container
*ngTemplateOutlet="
userLabel;
context: {
$implicit: { nzLabel: user.displayName, nzValue: user.id },
}
">
</ng-container>
</nz-option>
<ng-template
#userLabel
let-selected>
<span
nz-icon
[nzType]="'civic-curator'"
nzTheme="twotone"
[nzTwotoneColor]="'Curator' | entityColor"></span>
{{ selected.nzLabel }}
</ng-template>
</nz-select>
<cvc-user-filter-select
[cvcUniqueParticipants]="cvcFilterOptions().uniqueParticipants"
[cvcUserId]="userId()"
(cvcUserIdChange)="userId.set($event)"></cvc-user-filter-select>
</nz-col>
}

Expand All @@ -160,43 +131,14 @@
value: organizationId(),
}
"></ng-container>
<nz-select
nzPlaceHolder="Select Organization"
nzMode="multiple"
[ngModel]="organizationId()"
(ngModelChange)="organizationId.set($event)"
[nzCustomTemplate]="organizationLabel"
[nzOptionHeightPx]="28">
<nz-option
*ngFor="
let organization of cvcFilterOptions().participatingOrganizations
"
nzCustomContent
[nzLabel]="organization.name"
[nzValue]="organization.id">
<ng-container
*ngTemplateOutlet="
organizationLabel;
context: {
$implicit: {
nzLabel: organization.name,
nzValue: organization.id,
},
}
">
</ng-container>
</nz-option>
<ng-template
#organizationLabel
let-selected>
<span
nz-icon
[nzType]="'civic-organization'"
nzTheme="twotone"
[nzTwotoneColor]="'Organization' | entityColor"></span>
{{ selected.nzLabel }}
</ng-template>
</nz-select>
<cvc-org-filter-select
[cvcParticipatingOrganizations]="
cvcFilterOptions().participatingOrganizations
"
[cvcOrganizationId]="organizationId()"
(cvcOrganizationIdChange)="
organizationId.set($event)
"></cvc-org-filter-select>
</nz-col>
}
<nz-col
Expand Down Expand Up @@ -237,11 +179,15 @@ <h4>Sort Direction</h4>
</nz-option>
</nz-select>
</nz-col>
<!-- <nz-col nzSpan="24">
<!-- <nz-col
nzSpan="24"
style="max-height: 200px; overflow-y: auto">
<h4>Filters</h4>
<pre>{{ cvcFilters() | json }}</pre>
<h4>Filter Options</h4>
<pre>{{ cvcFilterOptions() | json }}</pre>
<h4>
Unique Participants ({{ cvcFilterOptions().uniqueParticipants.length }})$
</h4>
<pre>{{ cvcFilterOptions().uniqueParticipants | json }}</pre>
</nz-col> -->
</nz-row>
<!-- TEMPLATES -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import {
DateSortColumns,
Maybe,
SortDirection,
UserFilterSearchGQL,
UserFilterSearchQuery,
UserFilterSearchQueryVariables,
} from '@app/generated/civic.apollo'
import { CommonModule, KeyValuePipe } from '@angular/common'
import { FormsModule } from '@angular/forms'
Expand All @@ -40,12 +43,19 @@ import { disableDates } from '../activity-feed.functions'
import { toObservable, toSignal } from '@angular/core/rxjs-interop'
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'
import { tag } from 'rxjs-spy/operators'
import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators'
import { timer, filter, of } from 'rxjs'
import { isNonNullObject } from '@apollo/client/utilities'
import {
distinctUntilChanged,
map,
skip,
startWith,
switchMap,
} from 'rxjs/operators'
import { timer, filter, of, Subject, from } from 'rxjs'
import { isNonNulled } from 'rxjs-etc'
import { NzButtonModule } from 'ng-zorro-antd/button'
import { NzAlertModule } from 'ng-zorro-antd/alert'
import { CvcUserFilterSelect } from './user-filter-select/user-filter-select.component'
import { CvcOrgFilterSelect } from './org-filter-select/org-filter-select.component'

export const defaultFilters = {}

Expand All @@ -65,6 +75,8 @@ export const defaultFilters = {}
NzSelectModule,
NzDatePickerModule,
CvcPipesModule,
CvcUserFilterSelect,
CvcOrgFilterSelect,
],
templateUrl: './feed-filters.component.html',
styleUrls: ['./feed-filters.component.less'],
Expand Down Expand Up @@ -118,7 +130,17 @@ export class CvcActivityFeedFilterSelects implements OnInit {
sortByDirection: this.sortByDirection(),
})
})

/**
* Observable that emits the count of new activities since the last refresh.
*
* Behavior:
* - Only activates if cvcCheckInterval > 0, else emits 0
* - Polls the API at the specified interval (cvcCheckInterval in seconds)
* - Uses the current filter settings but only looks for activities after the last refresh
* - Emits 0 initially and then the count of new activities
* - Only emits when the count changes (uses distinctUntilChanged)
* - Resets to 0 when filters change via cvcRefreshChanges
*/
const newActivities$ = toObservable(this.cvcRefreshChanges).pipe(
filter(isNonNulled),
switchMap((refetchEvent) => {
Expand Down Expand Up @@ -151,6 +173,7 @@ export class CvcActivityFeedFilterSelects implements OnInit {
)
this.newActivities = toSignal(newActivities$, { initialValue: 0 })
}

ngOnInit(): void {
this.eventType = signal(this.cvcFilters().activityType)
this.subjectType = signal(this.cvcFilters().subjectType)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<nz-select
nzPlaceHolder="Select Organization"
nzMode="multiple"
nzAllowClear
[(ngModel)]="cvcOrganizationId"
(nzOnSearch)="onSearch$.next($event)"
[nzCustomTemplate]="organizationLabel"
[nzNotFoundContent]="notFound"
[nzOptionHeightPx]="28">
<nz-option
*ngFor="let organization of filteredOrganizations()"
nzCustomContent
[nzLabel]="organization.name"
[nzValue]="organization.id">
<ng-container
*ngTemplateOutlet="
organizationLabel;
context: {
$implicit: {
nzLabel: organization.name,
nzValue: organization.id,
},
}
">
</ng-container>
</nz-option>
</nz-select>

<ng-template
#organizationLabel
let-selected>
<span
nz-icon
[nzType]="'civic-organization'"
nzTheme="twotone"
[nzTwotoneColor]="'Organization' | entityColor"></span>
{{ selected.nzLabel }}
</ng-template>

<ng-template #notFound>
<span>No organizationsfound matching "{{ onSearch() }}"</span>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Component, input, model, Signal } from '@angular/core'
import { ActivityFeedFilterOptions } from '../../activity-feed.types'
import { CvcPipesModule } from '@app/core/pipes/pipes.module'
import { CommonModule } from '@angular/common'
import { NzSelectModule } from 'ng-zorro-antd/select'
import { NzIconModule } from 'ng-zorro-antd/icon'
import { FormsModule } from '@angular/forms'
import {
BrowseOrganization,
Organization,
OrgFilterSearchGQL,
OrgFilterSearchQuery,
OrgFilterSearchQueryVariables,
} from '@app/generated/civic.apollo'
import { from, map, Subject, switchMap } from 'rxjs'
import { QueryRef } from 'apollo-angular'
import { tag } from 'rxjs-spy/operators'
import { toSignal } from '@angular/core/rxjs-interop'

@Component({
selector: 'cvc-org-filter-select',
standalone: true,
imports: [
CommonModule,
FormsModule,
NzIconModule,
NzSelectModule,
CvcPipesModule,
],
templateUrl: './org-filter-select.component.html',
styleUrl: './org-filter-select.component.less',
})
export class CvcOrgFilterSelect {
cvcParticipatingOrganizations =
input.required<ActivityFeedFilterOptions['participatingOrganizations']>()
cvcOrganizationId = model.required<number[]>()

onSearch$: Subject<string>
onSearch: Signal<string>
queryRef!: QueryRef<OrgFilterSearchQuery, OrgFilterSearchQueryVariables>
filteredOrganizations: Signal<BrowseOrganization[]>

constructor(private gql: OrgFilterSearchGQL) {
this.onSearch$ = new Subject<string>()
const filteredOrganizations$ = this.onSearch$.pipe(
tag(`filteredOrganizations$`),
switchMap((nameStr) => {
const query = { name: nameStr, first: 25 }
if (this.queryRef) {
const refetch = this.queryRef.refetch({ name: nameStr })
return from(refetch)
} else {
this.queryRef = this.gql.watch({ name: nameStr })
return this.queryRef.valueChanges
}
}),
map(
(result) =>
result.data?.organizations.edges.map(
(e) => e.node! as BrowseOrganization
) ?? []
),
tag(`${this.constructor.name} filteredOrganizations$ after`)
)
this.filteredOrganizations = toSignal(filteredOrganizations$, {
initialValue: [],
})
this.onSearch = toSignal(this.onSearch$, { initialValue: '' })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
query OrgFilterSearch($name: String) {
organizations(name: $name) {
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
edges {
node {
id
name
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<nz-select
nzPlaceHolder="Select User"
nzMode="multiple"
nzAllowClear
[(ngModel)]="cvcUserId"
(nzOnSearch)="onSearch$.next($event)"
[nzCustomTemplate]="userLabel"
[nzNotFoundContent]="notFound"
[nzOptionOverflowSize]="12"
[nzOptionHeightPx]="28">
<nz-option
*ngFor="let user of filteredUsers()"
nzCustomContent
[nzLabel]="user.displayName"
[nzValue]="user.id">
<ng-container
*ngTemplateOutlet="
userLabel;
context: {
$implicit: {
nzLabel: user.displayName ?? user.name ?? user.username,
nzValue: user.id,
},
}
">
</ng-container>
</nz-option>
</nz-select>

<ng-template
#userLabel
let-selected>
<span
nz-icon
[nzType]="'civic-curator'"
nzTheme="twotone"
[nzTwotoneColor]="'Curator' | entityColor"></span>
{{ selected.nzLabel }}
</ng-template>

<ng-template #notFound>
<span>No users found matching "{{ onSearch() }}"</span>
</ng-template>
Loading
Loading