fix(chronik): surface REPLY events in Für-dich feed via youParticipated #297
@@ -12,6 +12,7 @@ public interface ActivityFeedRow {
|
|||||||
UUID getDocumentId();
|
UUID getDocumentId();
|
||||||
Instant getHappenedAt();
|
Instant getHappenedAt();
|
||||||
boolean isYouMentioned();
|
boolean isYouMentioned();
|
||||||
|
boolean isYouParticipated();
|
||||||
int getCount();
|
int getCount();
|
||||||
Instant getHappenedAtUntil();
|
Instant getHappenedAtUntil();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ public interface AuditLogQueryRepository extends JpaRepository<AuditLog, UUID> {
|
|||||||
CASE WHEN COUNT(*) > 1 THEN MAX(s.happened_at) ELSE NULL END AS happened_at_until,
|
CASE WHEN COUNT(*) > 1 THEN MAX(s.happened_at) ELSE NULL END AS happened_at_until,
|
||||||
COUNT(*)::int AS count,
|
COUNT(*)::int AS count,
|
||||||
BOOL_OR(s.kind = 'MENTION_CREATED'
|
BOOL_OR(s.kind = 'MENTION_CREATED'
|
||||||
AND s.payload->>'mentionedUserId' = :currentUserId) AS you_mentioned
|
AND s.payload->>'mentionedUserId' = :currentUserId) AS you_mentioned,
|
||||||
|
-- COMMENT_ADDED/MENTION_CREATED always have is_new_session=1, so each group has one row and MIN collapses to that row payload
|
||||||
|
MIN(s.payload::text)::jsonb AS payload
|
||||||
FROM sessions s
|
FROM sessions s
|
||||||
GROUP BY s.kind, s.actor_id, s.document_id, s.session_id
|
GROUP BY s.kind, s.actor_id, s.document_id, s.session_id
|
||||||
)
|
)
|
||||||
@@ -89,6 +91,13 @@ public interface AuditLogQueryRepository extends JpaRepository<AuditLog, UUID> {
|
|||||||
ag.document_id AS documentId,
|
ag.document_id AS documentId,
|
||||||
ag.happened_at AS happened_at,
|
ag.happened_at AS happened_at,
|
||||||
ag.you_mentioned AS youMentioned,
|
ag.you_mentioned AS youMentioned,
|
||||||
|
-- payload->>'commentId' matches notifications.reference_id per AuditKind.COMMENT_ADDED contract
|
||||||
|
EXISTS(
|
||||||
|
SELECT 1 FROM notifications n
|
||||||
|
WHERE n.type = 'REPLY'
|
||||||
|
AND n.recipient_id = CAST(:currentUserId AS uuid)
|
||||||
|
AND n.reference_id = (ag.payload->>'commentId')::uuid
|
||||||
|
) AS youParticipated,
|
||||||
ag.count AS count,
|
ag.count AS count,
|
||||||
ag.happened_at_until AS happenedAtUntil
|
ag.happened_at_until AS happenedAtUntil
|
||||||
FROM aggregated ag
|
FROM aggregated ag
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public record ActivityFeedItemDTO(
|
|||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) String documentTitle,
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) String documentTitle,
|
||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) OffsetDateTime happenedAt,
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) OffsetDateTime happenedAt,
|
||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) boolean youMentioned,
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) boolean youMentioned,
|
||||||
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) boolean youParticipated,
|
||||||
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) int count,
|
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) int count,
|
||||||
@Nullable OffsetDateTime happenedAtUntil
|
@Nullable OffsetDateTime happenedAtUntil
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ public class DashboardService {
|
|||||||
docTitle,
|
docTitle,
|
||||||
row.getHappenedAt().atOffset(ZoneOffset.UTC),
|
row.getHappenedAt().atOffset(ZoneOffset.UTC),
|
||||||
row.isYouMentioned(),
|
row.isYouMentioned(),
|
||||||
|
row.isYouParticipated(),
|
||||||
row.getCount(),
|
row.getCount(),
|
||||||
happenedAtUntil
|
happenedAtUntil
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,9 +14,13 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
|||||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@@ -28,9 +32,12 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
class AuditLogQueryRepositoryRolledUpTest {
|
class AuditLogQueryRepositoryRolledUpTest {
|
||||||
|
|
||||||
static final UUID USER_ID = UUID.fromString("dddddddd-dddd-dddd-dddd-dddddddddddd");
|
static final UUID USER_ID = UUID.fromString("dddddddd-dddd-dddd-dddd-dddddddddddd");
|
||||||
|
static final UUID OTHER_USER_ID = UUID.fromString("cccccccc-cccc-cccc-cccc-cccccccccccc");
|
||||||
static final UUID DOC_ID = UUID.fromString("eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee");
|
static final UUID DOC_ID = UUID.fromString("eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee");
|
||||||
static final UUID OTHER_DOC_ID = UUID.fromString("ffffffff-ffff-ffff-ffff-ffffffffffff");
|
static final UUID OTHER_DOC_ID = UUID.fromString("ffffffff-ffff-ffff-ffff-ffffffffffff");
|
||||||
|
|
||||||
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
@Autowired AuditLogQueryRepository auditLogQueryRepository;
|
@Autowired AuditLogQueryRepository auditLogQueryRepository;
|
||||||
@Autowired JdbcTemplate jdbcTemplate;
|
@Autowired JdbcTemplate jdbcTemplate;
|
||||||
|
|
||||||
@@ -42,6 +49,9 @@ class AuditLogQueryRepositoryRolledUpTest {
|
|||||||
jdbcTemplate.update(
|
jdbcTemplate.update(
|
||||||
"INSERT INTO users (id, enabled, email, password) VALUES (?, true, ?, 'pw')",
|
"INSERT INTO users (id, enabled, email, password) VALUES (?, true, ?, 'pw')",
|
||||||
USER_ID, "rollup-" + USER_ID + "@test.com");
|
USER_ID, "rollup-" + USER_ID + "@test.com");
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"INSERT INTO users (id, enabled, email, password) VALUES (?, true, ?, 'pw')",
|
||||||
|
OTHER_USER_ID, "rollup-" + OTHER_USER_ID + "@test.com");
|
||||||
jdbcTemplate.update(
|
jdbcTemplate.update(
|
||||||
"INSERT INTO documents (id, title, original_filename, status) VALUES (?, 'Brief A', 'a.pdf', 'PLACEHOLDER')",
|
"INSERT INTO documents (id, title, original_filename, status) VALUES (?, 'Brief A', 'a.pdf', 'PLACEHOLDER')",
|
||||||
DOC_ID);
|
DOC_ID);
|
||||||
@@ -51,17 +61,34 @@ class AuditLogQueryRepositoryRolledUpTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void insertAuditEvent(UUID actorId, UUID docId, String kind, Instant happenedAt) {
|
private void insertAuditEvent(UUID actorId, UUID docId, String kind, Instant happenedAt) {
|
||||||
|
insertAuditEvent(actorId, docId, kind, happenedAt, Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertAuditEvent(UUID actorId, UUID docId, String kind, Instant happenedAt, Map<String, String> payload) {
|
||||||
|
String payloadJson;
|
||||||
|
try {
|
||||||
|
payloadJson = payload.isEmpty() ? null : MAPPER.writeValueAsString(payload);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
MapSqlParameterSource params = new MapSqlParameterSource()
|
MapSqlParameterSource params = new MapSqlParameterSource()
|
||||||
.addValue("kind", kind)
|
.addValue("kind", kind)
|
||||||
.addValue("actor", actorId)
|
.addValue("actor", actorId)
|
||||||
.addValue("doc", docId)
|
.addValue("doc", docId)
|
||||||
.addValue("t", OffsetDateTime.ofInstant(happenedAt, java.time.ZoneOffset.UTC));
|
.addValue("t", OffsetDateTime.ofInstant(happenedAt, java.time.ZoneOffset.UTC))
|
||||||
|
.addValue("payload", payloadJson, java.sql.Types.OTHER);
|
||||||
named().update(
|
named().update(
|
||||||
"INSERT INTO audit_log (kind, actor_id, document_id, happened_at) "
|
"INSERT INTO audit_log (kind, actor_id, document_id, happened_at, payload) "
|
||||||
+ "VALUES (:kind, :actor, :doc, :t)",
|
+ "VALUES (:kind, :actor, :doc, :t, :payload::jsonb)",
|
||||||
params);
|
params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void insertReplyNotification(UUID recipientId, UUID docId, UUID commentId) {
|
||||||
|
jdbcTemplate.update(
|
||||||
|
"INSERT INTO notifications (recipient_id, type, document_id, reference_id) VALUES (?, 'REPLY', ?, ?)",
|
||||||
|
recipientId, docId, commentId);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void rolledUpFeed_combines_same_actor_same_doc_within_2h() {
|
void rolledUpFeed_combines_same_actor_same_doc_within_2h() {
|
||||||
insertUserAndDocs();
|
insertUserAndDocs();
|
||||||
@@ -173,4 +200,87 @@ class AuditLogQueryRepositoryRolledUpTest {
|
|||||||
assertThat(r.getHappenedAtUntil()).isNull();
|
assertThat(r.getHappenedAtUntil()).isNull();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void youParticipated_is_true_when_user_has_reply_notification_for_comment() {
|
||||||
|
insertUserAndDocs();
|
||||||
|
UUID commentId = UUID.randomUUID();
|
||||||
|
insertAuditEvent(OTHER_USER_ID, DOC_ID, "COMMENT_ADDED",
|
||||||
|
Instant.parse("2026-04-20T10:00:00Z"), Map.of("commentId", commentId.toString()));
|
||||||
|
insertReplyNotification(USER_ID, DOC_ID, commentId);
|
||||||
|
|
||||||
|
List<ActivityFeedRow> rows = auditLogQueryRepository.findRolledUpActivityFeed(USER_ID.toString(), 40);
|
||||||
|
|
||||||
|
assertThat(rows).anySatisfy(r ->
|
||||||
|
assertThat(r.isYouParticipated()).isTrue()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void youParticipated_is_false_for_comment_with_no_reply_notification() {
|
||||||
|
insertUserAndDocs();
|
||||||
|
UUID commentId = UUID.randomUUID();
|
||||||
|
insertAuditEvent(OTHER_USER_ID, DOC_ID, "COMMENT_ADDED",
|
||||||
|
Instant.parse("2026-04-20T10:00:00Z"), Map.of("commentId", commentId.toString()));
|
||||||
|
|
||||||
|
List<ActivityFeedRow> rows = auditLogQueryRepository.findRolledUpActivityFeed(USER_ID.toString(), 40);
|
||||||
|
|
||||||
|
assertThat(rows).allSatisfy(r ->
|
||||||
|
assertThat(r.isYouParticipated()).isFalse()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void youParticipated_is_false_when_comment_added_has_no_commentId_in_payload() {
|
||||||
|
insertUserAndDocs();
|
||||||
|
insertAuditEvent(OTHER_USER_ID, DOC_ID, "COMMENT_ADDED",
|
||||||
|
Instant.parse("2026-04-20T10:00:00Z"), Map.of());
|
||||||
|
|
||||||
|
List<ActivityFeedRow> rows = auditLogQueryRepository.findRolledUpActivityFeed(USER_ID.toString(), 40);
|
||||||
|
|
||||||
|
assertThat(rows).allSatisfy(r ->
|
||||||
|
assertThat(r.isYouParticipated()).isFalse()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void youParticipated_is_false_when_reply_notification_belongs_to_other_user() {
|
||||||
|
insertUserAndDocs();
|
||||||
|
UUID commentId = UUID.randomUUID();
|
||||||
|
insertAuditEvent(OTHER_USER_ID, DOC_ID, "COMMENT_ADDED",
|
||||||
|
Instant.parse("2026-04-20T10:00:00Z"), Map.of("commentId", commentId.toString()));
|
||||||
|
insertReplyNotification(OTHER_USER_ID, DOC_ID, commentId);
|
||||||
|
|
||||||
|
List<ActivityFeedRow> rows = auditLogQueryRepository.findRolledUpActivityFeed(USER_ID.toString(), 40);
|
||||||
|
|
||||||
|
assertThat(rows).allSatisfy(r ->
|
||||||
|
assertThat(r.isYouParticipated()).isFalse()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void youMentioned_is_true_when_mention_created_payload_matches_current_user() {
|
||||||
|
insertUserAndDocs();
|
||||||
|
insertAuditEvent(OTHER_USER_ID, DOC_ID, "MENTION_CREATED",
|
||||||
|
Instant.parse("2026-04-20T10:00:00Z"), Map.of("mentionedUserId", USER_ID.toString()));
|
||||||
|
|
||||||
|
List<ActivityFeedRow> rows = auditLogQueryRepository.findRolledUpActivityFeed(USER_ID.toString(), 40);
|
||||||
|
|
||||||
|
assertThat(rows).anySatisfy(r ->
|
||||||
|
assertThat(r.isYouMentioned()).isTrue()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void youMentioned_is_false_when_mention_created_payload_targets_different_user() {
|
||||||
|
insertUserAndDocs();
|
||||||
|
insertAuditEvent(USER_ID, DOC_ID, "MENTION_CREATED",
|
||||||
|
Instant.parse("2026-04-20T10:00:00Z"), Map.of("mentionedUserId", OTHER_USER_ID.toString()));
|
||||||
|
|
||||||
|
List<ActivityFeedRow> rows = auditLogQueryRepository.findRolledUpActivityFeed(USER_ID.toString(), 40);
|
||||||
|
|
||||||
|
assertThat(rows).allSatisfy(r ->
|
||||||
|
assertThat(r.isYouMentioned()).isFalse()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class DashboardServiceTest {
|
|||||||
public UUID getDocumentId() { return docId; }
|
public UUID getDocumentId() { return docId; }
|
||||||
public Instant getHappenedAt() { return Instant.now(); }
|
public Instant getHappenedAt() { return Instant.now(); }
|
||||||
public boolean isYouMentioned() { return false; }
|
public boolean isYouMentioned() { return false; }
|
||||||
|
public boolean isYouParticipated() { return false; }
|
||||||
public int getCount() { return 1; }
|
public int getCount() { return 1; }
|
||||||
public Instant getHappenedAtUntil() { return null; }
|
public Instant getHappenedAtUntil() { return null; }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2015,6 +2015,7 @@ export interface components {
|
|||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
happenedAt: string;
|
happenedAt: string;
|
||||||
youMentioned: boolean;
|
youMentioned: boolean;
|
||||||
|
youParticipated: boolean;
|
||||||
/** Format: int32 */
|
/** Format: int32 */
|
||||||
count: number;
|
count: number;
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import ChronikEmptyState from '$lib/components/chronik/ChronikEmptyState.svelte'
|
|||||||
import ChronikErrorCard from '$lib/components/chronik/ChronikErrorCard.svelte';
|
import ChronikErrorCard from '$lib/components/chronik/ChronikErrorCard.svelte';
|
||||||
import type { components } from '$lib/generated/api';
|
import type { components } from '$lib/generated/api';
|
||||||
import type { FilterValue } from './+page.server';
|
import type { FilterValue } from './+page.server';
|
||||||
|
import { filterFeed } from './feedFilters';
|
||||||
|
|
||||||
type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
|
type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
|
||||||
|
|
||||||
@@ -92,28 +93,7 @@ async function onMarkAllRead() {
|
|||||||
await notificationStore.markAllRead();
|
await notificationStore.markAllRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayFeed = $derived<ActivityFeedItemDTO[]>(
|
const displayFeed = $derived(filterFeed(data.activityFeed, activeFilter));
|
||||||
(() => {
|
|
||||||
const merged = data.activityFeed;
|
|
||||||
switch (activeFilter) {
|
|
||||||
case 'alle':
|
|
||||||
return merged;
|
|
||||||
case 'fuer-dich':
|
|
||||||
return merged.filter((i) => i.kind === 'MENTION_CREATED' || i.youMentioned);
|
|
||||||
case 'hochgeladen':
|
|
||||||
return merged.filter((i) => i.kind === 'FILE_UPLOADED');
|
|
||||||
case 'transkription':
|
|
||||||
return merged.filter(
|
|
||||||
(i) =>
|
|
||||||
i.kind === 'TEXT_SAVED' ||
|
|
||||||
i.kind === 'BLOCK_REVIEWED' ||
|
|
||||||
i.kind === 'ANNOTATION_CREATED'
|
|
||||||
);
|
|
||||||
case 'kommentare':
|
|
||||||
return merged.filter((i) => i.kind === 'COMMENT_ADDED' || i.kind === 'MENTION_CREATED');
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
|
|
||||||
const isEmpty = $derived(displayFeed.length === 0);
|
const isEmpty = $derived(displayFeed.length === 0);
|
||||||
const emptyVariant = $derived<'first-run' | 'filter-empty' | 'inbox-zero'>(
|
const emptyVariant = $derived<'first-run' | 'filter-empty' | 'inbox-zero'>(
|
||||||
|
|||||||
94
frontend/src/routes/chronik/feedFilters.test.ts
Normal file
94
frontend/src/routes/chronik/feedFilters.test.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { filterFeed } from './feedFilters';
|
||||||
|
import type { components } from '$lib/generated/api';
|
||||||
|
|
||||||
|
type Item = components['schemas']['ActivityFeedItemDTO'];
|
||||||
|
|
||||||
|
function makeItem(overrides: Partial<Item> = {}): Item {
|
||||||
|
return {
|
||||||
|
kind: 'FILE_UPLOADED',
|
||||||
|
documentId: 'd1',
|
||||||
|
documentTitle: 'Brief A',
|
||||||
|
happenedAt: '2026-04-20T10:00:00Z',
|
||||||
|
youMentioned: false,
|
||||||
|
youParticipated: false,
|
||||||
|
count: 1,
|
||||||
|
actor: null,
|
||||||
|
happenedAtUntil: null,
|
||||||
|
...overrides
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('filterFeed', () => {
|
||||||
|
describe('alle', () => {
|
||||||
|
it('returns all items regardless of kind', () => {
|
||||||
|
const items = [
|
||||||
|
makeItem({ kind: 'FILE_UPLOADED' }),
|
||||||
|
makeItem({ kind: 'COMMENT_ADDED' }),
|
||||||
|
makeItem({ kind: 'MENTION_CREATED' })
|
||||||
|
];
|
||||||
|
expect(filterFeed(items, 'alle')).toHaveLength(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fuer-dich', () => {
|
||||||
|
it('includes MENTION_CREATED items', () => {
|
||||||
|
const items = [makeItem({ kind: 'MENTION_CREATED' })];
|
||||||
|
expect(filterFeed(items, 'fuer-dich')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes items where youMentioned is true', () => {
|
||||||
|
const items = [makeItem({ kind: 'COMMENT_ADDED', youMentioned: true })];
|
||||||
|
expect(filterFeed(items, 'fuer-dich')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes items where youParticipated is true', () => {
|
||||||
|
const items = [makeItem({ kind: 'COMMENT_ADDED', youParticipated: true })];
|
||||||
|
expect(filterFeed(items, 'fuer-dich')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('excludes FILE_UPLOADED with no participation', () => {
|
||||||
|
const items = [makeItem({ kind: 'FILE_UPLOADED' })];
|
||||||
|
expect(filterFeed(items, 'fuer-dich')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('excludes COMMENT_ADDED with no mention and no participation', () => {
|
||||||
|
const items = [makeItem({ kind: 'COMMENT_ADDED' })];
|
||||||
|
expect(filterFeed(items, 'fuer-dich')).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hochgeladen', () => {
|
||||||
|
it('includes only FILE_UPLOADED items', () => {
|
||||||
|
const items = [
|
||||||
|
makeItem({ kind: 'FILE_UPLOADED' }),
|
||||||
|
makeItem({ kind: 'COMMENT_ADDED', youParticipated: true })
|
||||||
|
];
|
||||||
|
expect(filterFeed(items, 'hochgeladen')).toHaveLength(1);
|
||||||
|
expect(filterFeed(items, 'hochgeladen')[0].kind).toBe('FILE_UPLOADED');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('transkription', () => {
|
||||||
|
it('includes TEXT_SAVED, BLOCK_REVIEWED, ANNOTATION_CREATED', () => {
|
||||||
|
const items = [
|
||||||
|
makeItem({ kind: 'TEXT_SAVED' }),
|
||||||
|
makeItem({ kind: 'BLOCK_REVIEWED' }),
|
||||||
|
makeItem({ kind: 'ANNOTATION_CREATED' }),
|
||||||
|
makeItem({ kind: 'FILE_UPLOADED' })
|
||||||
|
];
|
||||||
|
expect(filterFeed(items, 'transkription')).toHaveLength(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('kommentare', () => {
|
||||||
|
it('includes COMMENT_ADDED and MENTION_CREATED', () => {
|
||||||
|
const items = [
|
||||||
|
makeItem({ kind: 'COMMENT_ADDED' }),
|
||||||
|
makeItem({ kind: 'MENTION_CREATED' }),
|
||||||
|
makeItem({ kind: 'FILE_UPLOADED' })
|
||||||
|
];
|
||||||
|
expect(filterFeed(items, 'kommentare')).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
27
frontend/src/routes/chronik/feedFilters.ts
Normal file
27
frontend/src/routes/chronik/feedFilters.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import type { components } from '$lib/generated/api';
|
||||||
|
import type { FilterValue } from './+page.server';
|
||||||
|
|
||||||
|
type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
|
||||||
|
|
||||||
|
export function filterFeed(
|
||||||
|
items: ActivityFeedItemDTO[],
|
||||||
|
filter: FilterValue
|
||||||
|
): ActivityFeedItemDTO[] {
|
||||||
|
switch (filter) {
|
||||||
|
case 'alle':
|
||||||
|
return items;
|
||||||
|
case 'fuer-dich':
|
||||||
|
return items.filter(
|
||||||
|
(i) => i.kind === 'MENTION_CREATED' || i.youMentioned || i.youParticipated
|
||||||
|
);
|
||||||
|
case 'hochgeladen':
|
||||||
|
return items.filter((i) => i.kind === 'FILE_UPLOADED');
|
||||||
|
case 'transkription':
|
||||||
|
return items.filter(
|
||||||
|
(i) =>
|
||||||
|
i.kind === 'TEXT_SAVED' || i.kind === 'BLOCK_REVIEWED' || i.kind === 'ANNOTATION_CREATED'
|
||||||
|
);
|
||||||
|
case 'kommentare':
|
||||||
|
return items.filter((i) => i.kind === 'COMMENT_ADDED' || i.kind === 'MENTION_CREATED');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user