Skip to content

Commit 13d590e

Browse files
authored
Fix translation worker lock and project page (#1127)
* fix(content-translation-worker): lock work rows Restrict the claim query's SKIP LOCKED row lock to content_translation_work so PostgreSQL does not try to lock the nullable conversation side of the outer join. This preserves concurrent worker claim behavior while avoiding the production FOR UPDATE outer-join error. Add a regression test that compiles the claim query with the PostgreSQL dialect and asserts the generated SQL uses FOR UPDATE OF content_translation_work SKIP LOCKED. Deploy: content-translation-worker * fix(agora): hide project activity range Remove the visible activity range label from project pages while keeping the existing infinite-scroll list and completion state intact. Also remove the unused activityCount prop, list-progress styles, and obsolete project page translation entries so the component API matches the rendered UI. Deploy: agora * fix(agora): wait for auth on project page Ensure the project page does not fetch until frontend authentication state has initialized. This lets the backend resolve the participant's stored project display language before returning localized project activity card content, instead of issuing an early unauthenticated request that can fall back to the wrong language. The conversation page flow is unchanged.\n\nDeploy: agora * fix(agora): render plain text literally ZKPlainTextContent was routing plain text through the rich HTML renderer by escaping it into HTML, then sanitizing and rendering it with v-html. That made the component behavior confusing and left linkification/newline handling available on something named plain text. Render plain text directly through Vue interpolation instead. This keeps HTML markup as literal text, removes the unused enableLinks prop from the plain-text component API, updates the two project page callers, and deletes the now-dead plainTextToSafeHtml helper and its tests. Deploy: agora * fix(agora): show project participants Always render the project header participant count so it matches the votes and activities stats. Deploy: agora
1 parent c7cfe30 commit 13d590e

10 files changed

Lines changed: 41 additions & 83 deletions

File tree

services/agora/src/components/project/ProjectActivityCard.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
class="activity-card__body"
3434
:plain-text="activity.bodyPlainText"
3535
:compact-mode="true"
36-
:enable-links="false"
3736
:compact-line-count="3"
3837
/>
3938

services/agora/src/components/project/ProjectPageView.vue

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,12 @@
6363
class="project-page-view__body"
6464
:plain-text="project.bodyPlainText"
6565
:compact-mode="false"
66-
:enable-links="true"
6766
:collapsed-line-count="4"
6867
:desktop-collapsed-line-count="4"
6968
/>
7069

7170
<div class="project-page-view__stats">
72-
<span v-if="project.participantCount >= 2">
71+
<span>
7372
<q-icon name="mdi-account-outline" size="1rem" />
7473
{{
7574
t("participantsJoined", {
@@ -110,18 +109,6 @@
110109
{{ t("activitiesTitle") }}
111110
</h2>
112111
</div>
113-
114-
<span
115-
v-if="activities.length > 0"
116-
class="project-page-view__list-progress"
117-
>
118-
{{
119-
t("showingActivities", {
120-
visible: formatNumber(activities.length),
121-
total: formatNumber(activityCount),
122-
})
123-
}}
124-
</span>
125112
</div>
126113

127114
<q-infinite-scroll
@@ -250,7 +237,6 @@ type ConsultationStatus = "none" | "live" | "closed";
250237
const props = defineProps<{
251238
project: ProjectPageData;
252239
activities: readonly ProjectActivity[];
253-
activityCount: number;
254240
canLoadMoreActivities: boolean;
255241
isLoadingMoreActivities: boolean;
256242
languageOptions: readonly ProjectLanguageOption[];
@@ -560,20 +546,9 @@ h1 {
560546
}
561547
562548
.project-page-view__section-heading {
563-
display: flex;
564-
align-items: end;
565-
justify-content: space-between;
566-
gap: 1rem;
567549
margin-bottom: 1rem;
568550
}
569551
570-
.project-page-view__list-progress {
571-
color: $ink-light;
572-
font-size: 0.85rem;
573-
font-weight: var(--font-weight-semibold);
574-
white-space: nowrap;
575-
}
576-
577552
h2 {
578553
margin: 0.25rem 0 0;
579554
color: $ink-darkest;

services/agora/src/components/project/projectPageI18n.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ export interface ProjectPageTranslations {
1515
activitiesCount: string;
1616
votesCount: string;
1717
activitiesTitle: string;
18-
showingActivities: string;
1918
emptyActivities: string;
2019
allActivitiesLoaded: string;
2120
projectDetailsAriaLabel: string;
@@ -62,7 +61,6 @@ export const projectPageTranslations: Readonly<
6261
activitiesCount: "{count} activities",
6362
votesCount: "{count} votes",
6463
activitiesTitle: "Activities",
65-
showingActivities: "Showing {visible} of {total}",
6664
emptyActivities: "No activities have been published yet.",
6765
allActivitiesLoaded: "All activities loaded",
6866
projectDetailsAriaLabel: "Project details",
@@ -104,7 +102,6 @@ export const projectPageTranslations: Readonly<
104102
activitiesCount: "{count} иш-чара",
105103
votesCount: "{count} добуш",
106104
activitiesTitle: "Иш-чаралар",
107-
showingActivities: "{visible} / {total} көрсөтүлүүдө",
108105
emptyActivities: "Азырынча иш-чаралар жарыялана элек.",
109106
allActivitiesLoaded: "Бардык иш-чаралар жүктөлдү",
110107
projectDetailsAriaLabel: "Долбоор тууралуу маалымат",
@@ -145,7 +142,6 @@ export const projectPageTranslations: Readonly<
145142
activitiesCount: "{count} активности",
146143
votesCount: "{count} голосов",
147144
activitiesTitle: "Активности",
148-
showingActivities: "Показано {visible} из {total}",
149145
emptyActivities: "Активности пока не опубликованы.",
150146
allActivitiesLoaded: "Все активности загружены",
151147
projectDetailsAriaLabel: "Информация о проекте",
Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,27 @@
11
<template>
2-
<ZKHtmlContent
3-
:html-body="htmlBody"
2+
<ZKContentDisclosure
43
:compact-mode="compactMode"
5-
:enable-links="enableLinks"
6-
:content-role="contentRole"
7-
:collapsible="collapsible"
4+
:disclosure-enabled="isDisclosureEnabled"
85
:collapsed-line-count="collapsedLineCount"
96
:desktop-collapsed-line-count="desktopCollapsedLineCount"
107
:compact-line-count="compactLineCount"
11-
/>
8+
:refresh-key="plainText"
9+
>
10+
{{ plainText }}
11+
</ZKContentDisclosure>
1212
</template>
1313

1414
<script setup lang="ts">
15-
import { plainTextToSafeHtml } from "src/utils/html/plainText";
1615
import { computed } from "vue";
1716
18-
import ZKHtmlContent from "./ZKHtmlContent.vue";
17+
import ZKContentDisclosure from "./ZKContentDisclosure.vue";
1918
2019
type PlainTextContentRole = "body" | "title";
2120
2221
const props = withDefaults(
2322
defineProps<{
2423
plainText: string;
2524
compactMode: boolean;
26-
enableLinks: boolean;
2725
contentRole?: PlainTextContentRole;
2826
collapsible?: boolean;
2927
collapsedLineCount?: number;
@@ -39,7 +37,7 @@ const props = withDefaults(
3937
}
4038
);
4139
42-
const htmlBody = computed(() =>
43-
plainTextToSafeHtml({ plainText: props.plainText })
40+
const isDisclosureEnabled = computed(
41+
() => props.collapsible && props.contentRole === "body" && !props.compactMode
4442
);
4543
</script>

services/agora/src/pages/dev/project-page.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
v-model:selected-language="selectedLanguage"
7878
:project="project"
7979
:activities="activities"
80-
:activity-count="activities.length"
8180
:can-load-more-activities="false"
8281
:is-loading-more-activities="false"
8382
:language-options="languageOptions"

services/agora/src/pages/project/[projectSlug].vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
v-model:selected-language="selectedLanguage"
1414
:project="projectPageData.project"
1515
:activities="activities"
16-
:activity-count="projectPageData.project.activityCount"
1716
:can-load-more-activities="nextActivityCursor !== undefined"
1817
:is-loading-more-activities="isLoadingMoreActivities"
1918
:language-options="projectPageData.languageOptions"
@@ -47,7 +46,9 @@ const activityPageSize = 12;
4746
const route = useRoute();
4847
const { fetchProjectPage, fetchProjectPageActivities, updateProjectPageDisplayLanguage } =
4948
useBackendProjectPageApi();
50-
const { isGuestOrLoggedIn } = storeToRefs(useAuthenticationStore());
49+
const { isAuthInitialized, isGuestOrLoggedIn } = storeToRefs(
50+
useAuthenticationStore()
51+
);
5152
5253
const projectSlug = computed(() => getSingleRouteParam(route.params.projectSlug));
5354
const selectedLanguage = ref<string | readonly string[]>("");
@@ -85,7 +86,7 @@ const projectPageQuery = useQuery({
8586
},
8687
authenticated: isGuestOrLoggedIn.value,
8788
}),
88-
enabled: computed(() => projectSlug.value !== ""),
89+
enabled: computed(() => projectSlug.value !== "" && isAuthInitialized.value),
8990
retry: false,
9091
});
9192
const projectPageData = computed(() => projectPageQuery.data.value);

services/agora/src/utils/html/plainText.test.ts

Lines changed: 0 additions & 29 deletions
This file was deleted.

services/agora/src/utils/html/plainText.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

services/content-translation-worker/src/content_translation_worker/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ def claim_content_translation_work_batch(
442442
ContentTranslationWork.id.asc(),
443443
)
444444
.limit(batch_size)
445-
.with_for_update(skip_locked=True)
445+
.with_for_update(of=ContentTranslationWork, skip_locked=True)
446446
)
447447

448448
claims: list[ClaimedContentTranslationWork] = []
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from __future__ import annotations
2+
3+
from unittest.mock import create_autospec
4+
5+
from sqlalchemy.dialects.postgresql import dialect as postgresql_dialect
6+
from sqlalchemy.orm import Session
7+
8+
from content_translation_worker.db import claim_content_translation_work_batch
9+
10+
11+
def test_claim_query_locks_only_content_translation_work() -> None:
12+
session = create_autospec(Session, instance=True)
13+
session.execute.return_value = []
14+
15+
claim_content_translation_work_batch(
16+
session,
17+
worker_id="worker-1",
18+
work_ids=None,
19+
batch_size=10,
20+
lease_ttl_seconds=30,
21+
)
22+
23+
statement = session.execute.call_args.args[0]
24+
sql = str(statement.compile(dialect=postgresql_dialect()))
25+
26+
assert "FOR UPDATE OF content_translation_work SKIP LOCKED" in sql

0 commit comments

Comments
 (0)