Skip to content

Commit

Permalink
feat(api-service,dashboard): subscriber activity list and filters (#7677
Browse files Browse the repository at this point in the history
)
  • Loading branch information
LetItRock authored Feb 10, 2025
1 parent c9f38c0 commit 421de97
Show file tree
Hide file tree
Showing 21 changed files with 504 additions and 210 deletions.
22 changes: 20 additions & 2 deletions apps/api/src/app/notifications/dtos/activities-request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Type } from 'class-transformer';
import { IsOptional, IsInt, Max, Min } from 'class-validator';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { ChannelTypeEnum } from '@novu/shared';
import { IsOptional } from 'class-validator';

export class ActivitiesRequestDto {
@ApiPropertyOptional({
Expand Down Expand Up @@ -50,7 +51,24 @@ export class ActivitiesRequestDto {
description: 'Page number for pagination',
})
@IsOptional()
page?: number;
@Type(() => Number)
@IsInt()
@Min(0)
page: number = 0;

@ApiPropertyOptional({
type: Number,
default: 10,
minimum: 1,
maximum: 50,
description: 'Limit for pagination',
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(50)
limit: number = 10;

@ApiPropertyOptional({
type: String,
Expand Down
11 changes: 11 additions & 0 deletions apps/api/src/app/notifications/dtos/activities-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ProvidersIdEnumConst,
StepTypeEnum,
TriggerTypeEnum,
WorkflowOriginEnum,
} from '@novu/shared';
import { IsArray, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator';
import { StepFilterDto } from '../../shared/dtos/step-filter-dto';
Expand Down Expand Up @@ -313,6 +314,16 @@ export class ActivityNotificationTemplateResponseDto {
@ApiProperty({ description: 'Name of the template', type: String })
name: string;

@ApiProperty({
enum: [...Object.values(WorkflowOriginEnum)],
enumName: 'WorkflowOriginEnum',
description: 'Origin of the workflow',
type: String,
})
@IsString()
@IsEnum(WorkflowOriginEnum)
origin?: WorkflowOriginEnum;

@ApiProperty({
description: 'Triggers of the template',
type: [NotificationTriggerDto],
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/app/notifications/notification.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export class NotificationsController {

return this.getActivityFeedUsecase.execute(
GetActivityFeedCommand.create({
page: query.page ? Number(query.page) : 0,
page: query.page,
limit: query.limit,
organizationId: user.organizationId,
environmentId: user.environmentId,
userId: user._id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { EnvironmentWithUserCommand } from '../../../shared/commands/project.com

export class GetActivityFeedCommand extends EnvironmentWithUserCommand {
@IsNumber()
@IsOptional()
page: number;

@IsNumber()
limit: number;

@IsOptional()
@IsEnum(ChannelTypeEnum, {
each: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { ActivitiesResponseDto } from '../../dtos/activities-response.dto';
import { GetActivityFeedCommand } from './get-activity-feed.command';
import { mapFeedItemToDto } from './map-feed-item-to.dto';

const LIMIT = 10;
@Injectable()
export class GetActivityFeed {
constructor(
Expand All @@ -23,7 +22,7 @@ export class GetActivityFeed {
return {
page: 0,
hasMore: false,
pageSize: LIMIT,
pageSize: command.limit,
data: [],
};
}
Expand All @@ -32,8 +31,8 @@ export class GetActivityFeed {

return {
page: command.page,
hasMore: notifications?.length === LIMIT,
pageSize: LIMIT,
hasMore: notifications?.length === command.limit,
pageSize: command.limit,
data: notifications.map((notification) => mapFeedItemToDto(notification)),
};
}
Expand Down Expand Up @@ -63,8 +62,8 @@ export class GetActivityFeed {
after: command.after,
before: command.before,
},
command.page * LIMIT,
LIMIT
command.page * command.limit,
command.limit
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function buildTemplate(template: TemplateFeedItem): ActivityNotificationTemplate
_id: template._id,
name: template.name,
triggers: template.triggers,
origin: template.origin,
};
}

Expand Down
20 changes: 14 additions & 6 deletions apps/dashboard/src/api/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@ interface ActivityResponse {
pageSize: number;
}

export function getActivityList(
environment: IEnvironment,
page = 0,
filters?: ActivityFilters,
signal?: AbortSignal
): Promise<ActivityResponse> {
export function getActivityList({
environment,
page,
limit,
filters,
signal,
}: {
environment: IEnvironment;
page: number;
limit: number;
filters?: ActivityFilters;
signal?: AbortSignal;
}): Promise<ActivityResponse> {
const searchParams = new URLSearchParams();
searchParams.append('page', page.toString());
searchParams.append('limit', limit.toString());

if (filters?.channels?.length) {
filters.channels.forEach((channel) => {
Expand Down
20 changes: 15 additions & 5 deletions apps/dashboard/src/components/activity/activity-empty-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@ import { ExternalLink } from '../shared/external-link';
interface ActivityEmptyStateProps {
className?: string;
emptySearchResults?: boolean;
emptySearchTitle?: string;
emptySearchDescription?: string;
emptyFiltersTitle?: string;
emptyFiltersDescription?: string;
onClearFilters?: () => void;
}

export function ActivityEmptyState({ className, emptySearchResults, onClearFilters }: ActivityEmptyStateProps) {
export function ActivityEmptyState({
className,
emptySearchResults,
onClearFilters,
emptySearchTitle = 'No activity match that filter',
emptySearchDescription = 'Try adjusting your filters to see more results.',
emptyFiltersTitle = 'No activity in the past 30 days',
emptyFiltersDescription = 'Your activity feed is empty. Once you trigger your first workflow, you can monitor notifications and view delivery details.',
}: ActivityEmptyStateProps) {
const navigate = useNavigate();
const { currentEnvironment } = useEnvironment();

Expand Down Expand Up @@ -67,12 +79,10 @@ export function ActivityEmptyState({ className, emptySearchResults, onClearFilte
className="flex flex-col items-center gap-1 text-center"
>
<h2 className="text-foreground-900 text-lg font-medium">
{emptySearchResults ? 'No activity match that filter' : 'No activity in the past 30 days'}
{emptySearchResults ? emptySearchTitle : emptyFiltersTitle}
</h2>
<p className="text-foreground-600 max-w-md text-sm font-normal">
{emptySearchResults
? 'Change your search criteria.'
: 'Your activity feed is empty. Once you trigger your first workflow, you can monitor notifications and view delivery details.'}
{emptySearchResults ? emptySearchDescription : emptyFiltersDescription}
</p>
</motion.div>

Expand Down
Loading

0 comments on commit 421de97

Please sign in to comment.