All files / src/hooks useTopics.ts

100% Statements 30/30
100% Branches 0/0
100% Functions 23/23
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173                                              3x   29x 18x 25x 24x 12x 3x                       17x   8x                             12x   5x                             11x   4x                             3x   1x                             11x   11x 6x     4x                           11x   11x   6x     4x 4x                           15x   15x             8x     5x   5x   5x        
/**
 * Topics React Query Hooks (Story 5.2 - Task 5b)
 *
 * Custom hooks for topic data fetching and mutations
 * Features:
 * - React Query for server state caching
 * - Automatic cache invalidation on updates
 * - 5 minute stale time for topic list (frequent updates)
 */
 
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { topicService } from '@/services/topicService';
import type {
  CreateTopicRequest,
  OverrideStalenesRequest,
  TopicFilters,
  SelectTopicForEventRequest,
} from '@/types/topic.types';
 
/**
 * Query key factory for topics
 * Centralizes query key management for cache invalidation
 */
export const topicKeys = {
  all: ['topics'] as const,
  lists: () => [...topicKeys.all, 'list'] as const,
  list: (filters?: TopicFilters) => [...topicKeys.lists(), filters] as const,
  details: () => [...topicKeys.all, 'detail'] as const,
  detail: (id: string) => [...topicKeys.details(), id] as const,
  similar: (id: string) => [...topicKeys.all, 'similar', id] as const,
  usageHistory: (id: string) => [...topicKeys.all, 'usage-history', id] as const,
};
 
/**
 * Hook to fetch topics with filtering and pagination (AC1)
 *
 * @param filters Optional filters for category, status, pagination, etc.
 * @returns Query result with paginated topic list
 * @example
 * const { data, isLoading } = useTopics({ category: 'Cloud Native', page: 1, limit: 20 });
 */
export function useTopics(filters?: TopicFilters) {
  return useQuery({
    queryKey: topicKeys.list(filters),
    queryFn: () => topicService.getTopics(filters),
    staleTime: 1000 * 60 * 5, // 5 minutes - topics change moderately
  });
}
 
/**
 * Hook to fetch specific topic by ID
 *
 * @param id Topic UUID
 * @param include Optional comma-separated resources to include
 * @returns Query result with topic details
 * @example
 * const { data: topic } = useTopic('uuid', 'similarity,history');
 */
export function useTopic(id: string, include?: string) {
  return useQuery({
    queryKey: topicKeys.detail(id),
    queryFn: () => topicService.getTopicById(id, include),
    enabled: !!id,
    staleTime: 1000 * 60 * 5, // 5 minutes
  });
}
 
/**
 * Hook to get similar topics for duplicate detection (AC5)
 *
 * @param id Topic UUID
 * @returns Query result with list of similar topics
 * @example
 * const { data: similarTopics } = useSimilarTopics('uuid');
 */
export function useSimilarTopics(id: string) {
  return useQuery({
    queryKey: topicKeys.similar(id),
    queryFn: () => topicService.getSimilarTopics(id),
    enabled: !!id,
    staleTime: 1000 * 60 * 60, // 1 hour - similarity scores change rarely
  });
}
 
/**
 * Hook to fetch topic usage history for heat map visualization (AC2)
 *
 * @param id Topic UUID
 * @returns Query result with array of usage history records
 * @example
 * const { data: usageHistory } = useTopicUsageHistory('uuid');
 */
export function useTopicUsageHistory(id: string) {
  return useQuery({
    queryKey: topicKeys.usageHistory(id),
    queryFn: () => topicService.getTopicUsageHistory(id),
    enabled: !!id,
    staleTime: 1000 * 60 * 60, // 1 hour - usage history changes rarely
  });
}
 
/**
 * Hook to create new topic (AC8, ORGANIZER only)
 *
 * @returns Mutation object with mutate function
 * @example
 * const createMutation = useCreateTopic();
 * createMutation.mutate({ title: 'New Topic', category: 'Technical', description: '...' });
 */
export function useCreateTopic() {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: (request: CreateTopicRequest) => topicService.createTopic(request),
    onSuccess: () => {
      // Invalidate all topic list queries to refetch fresh data
      queryClient.invalidateQueries({ queryKey: topicKeys.lists() });
    },
  });
}
 
/**
 * Hook to override staleness score with justification (AC7, ORGANIZER only)
 *
 * @returns Mutation object with mutate function
 * @example
 * const overrideMutation = useOverrideStaleness();
 * overrideMutation.mutate({ id: 'uuid', request: { stalenessScore: 80, justification: '...' } });
 */
export function useOverrideStaleness() {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: ({ id, request }: { id: string; request: OverrideStalenesRequest }) =>
      topicService.overrideStaleness(id, request),
    onSuccess: (_data, variables) => {
      // Invalidate specific topic and all lists
      queryClient.invalidateQueries({ queryKey: topicKeys.detail(variables.id) });
      queryClient.invalidateQueries({ queryKey: topicKeys.lists() });
    },
  });
}
 
/**
 * Hook to select topic for event and transition workflow state (AC14, ORGANIZER only)
 *
 * @returns Mutation object with mutate function
 * @example
 * const selectMutation = useSelectTopicForEvent();
 * selectMutation.mutate({ eventCode: 'BATbern56', topicId: 'uuid', justification: '...' });
 */
export function useSelectTopicForEvent() {
  const queryClient = useQueryClient();
 
  return useMutation({
    mutationFn: ({
      eventCode,
      request,
    }: {
      eventCode: string;
      request: SelectTopicForEventRequest;
    }) => topicService.selectTopicForEvent(eventCode, request),
    onSuccess: (_data, variables) => {
      // Invalidate event detail query to refetch updated event with new topicCode
      queryClient.invalidateQueries({ queryKey: ['event', variables.eventCode] });
      // Invalidate events list query to update list view
      queryClient.invalidateQueries({ queryKey: ['events'] });
      // Update topic status if applicable
      queryClient.invalidateQueries({ queryKey: topicKeys.detail(variables.request.topicCode) });
    },
  });
}