Skip to content

Commit

Permalink
Merge pull request #1149 from griffithlab/activity-feed-final
Browse files Browse the repository at this point in the history
Activity Feed's User and Organization filters search all users/organizations
  • Loading branch information
acoffman authored Oct 30, 2024
2 parents e0195b2 + 8179a69 commit c7482b9
Show file tree
Hide file tree
Showing 12 changed files with 390 additions and 78 deletions.
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

0 comments on commit c7482b9

Please sign in to comment.