Skip to content

Commit e426b2e

Browse files
authored
Merge pull request #1043 from opencrvs/ocrvs-10558
[OCRVS-10558] Fix reindexing output
2 parents 48df126 + 810130f commit e426b2e

File tree

6 files changed

+211
-16
lines changed

6 files changed

+211
-16
lines changed

.github/workflows/deploy.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ jobs:
113113
working-directory: ${{ github.event.repository.name }}
114114
run: |
115115
jq -n -r '
116-
[${{ toJSON(secrets) }}, ${{ toJSON(vars) }}]
116+
[${{ toJSON(secrets) }}, ${{ toJSON(vars) }}]
117117
| add
118118
| to_entries
119119
| map(select(.value | test("\n") | not))
@@ -157,3 +157,21 @@ jobs:
157157
environment: ${{ inputs.environment }}
158158
core-image-tag: ${{ inputs.core-image-tag }}
159159
secrets: inherit
160+
161+
reindex-after-seed:
162+
needs: seed-data
163+
if: ${{ inputs.reset == 'true' && needs.seed-data.result == 'success' }}
164+
uses: ./.github/workflows/reindex.yml
165+
with:
166+
environment: ${{ inputs.environment }}
167+
core-image-tag: ${{ inputs.core-image-tag }}
168+
secrets: inherit
169+
170+
reindex-after-deploy:
171+
needs: deploy
172+
if: ${{ inputs.reset != 'true' && needs.deploy.result == 'success' }}
173+
uses: ./.github/workflows/reindex.yml
174+
with:
175+
environment: ${{ inputs.environment }}
176+
core-image-tag: ${{ inputs.core-image-tag }}
177+
secrets: inherit

.github/workflows/reindex.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Reindex search
2+
run-name: Reindex search in ${{ inputs.environment }} core=${{ inputs.core-image-tag }}
3+
on:
4+
workflow_call:
5+
inputs:
6+
environment:
7+
required: true
8+
type: string
9+
core-image-tag:
10+
required: true
11+
type: string
12+
workflow_dispatch:
13+
inputs:
14+
environment:
15+
type: choice
16+
description: Environment where to reindex search
17+
required: true
18+
default: 'development'
19+
options:
20+
- development
21+
- qa
22+
- staging
23+
- production
24+
core-image-tag:
25+
description: Core DockerHub image tag
26+
required: true
27+
jobs:
28+
reindex:
29+
environment: ${{ inputs.environment }}
30+
runs-on: ubuntu-24.04
31+
timeout-minutes: 60
32+
33+
steps:
34+
- name: Clone country config resource package
35+
uses: actions/checkout@v3
36+
with:
37+
fetch-depth: 0
38+
ref: ${{ github.ref_name }}
39+
path: './${{ github.event.repository.name }}'
40+
41+
- name: Read known hosts
42+
run: |
43+
cd ${{ github.event.repository.name }}
44+
echo "KNOWN_HOSTS<<EOF" >> $GITHUB_ENV
45+
sed -i -e '$a\' ./infrastructure/known-hosts
46+
cat ./infrastructure/known-hosts >> $GITHUB_ENV
47+
echo "EOF" >> $GITHUB_ENV
48+
49+
- name: Install SSH Key
50+
uses: shimataro/ssh-key-action@v2
51+
with:
52+
key: ${{ secrets.SSH_KEY }}
53+
known_hosts: ${{ env.KNOWN_HOSTS }}
54+
55+
- name: Run reindex script in docker container
56+
run: |
57+
ssh -p ${{ vars.SSH_PORT }} ${{ secrets.SSH_USER }}@${{ vars.SSH_HOST }} ${{ vars.SSH_ARGS }} "
58+
docker run --rm \
59+
-v /opt/opencrvs/infrastructure/deployment:/workspace \
60+
-w /workspace \
61+
--network opencrvs_overlay_net \
62+
-e 'AUTH_URL=http://auth:4040/' \
63+
-e 'EVENTS_URL=http://gateway:7070/events/reindex' \
64+
alpine \
65+
sh -c 'apk add --no-cache curl jq && sh reindex.sh'"
66+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# This Source Code Form is subject to the terms of the Mozilla Public
2+
# License, v. 2.0. If a copy of the MPL was not distributed with this
3+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
#
5+
# OpenCRVS is also distributed under the terms of the Civil Registration
6+
# & Healthcare Disclaimer located at http://opencrvs.org/license.
7+
#
8+
# Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS.
9+
10+
#!/usr/bin/env bash
11+
set -euo pipefail
12+
13+
EVENTS_URL="${EVENTS_URL:-http://localhost:5555/}"
14+
AUTH_URL="${AUTH_URL:-http://localhost:4040/}"
15+
16+
17+
get_reindexing_token() {
18+
curl -s "${AUTH_URL%/}/internal/reindexing-token" | jq -r '.token'
19+
}
20+
21+
trigger_reindex() {
22+
token="$(get_reindexing_token)"
23+
out="$(curl -s -w '\n%{http_code}' -X POST \
24+
-H "Authorization: Bearer ${token}" \
25+
-H "Content-Type: application/json" \
26+
"${EVENTS_URL%/}/events/reindex")"
27+
body="$(printf '%s' "$out" | sed '$d')"
28+
code="$(printf '%s' "$out" | tail -n1)"
29+
echo "$body"
30+
[ "$code" -ge 200 ] && [ "$code" -lt 300 ]
31+
}
32+
33+
reindexing_attempts=0
34+
while true; do
35+
if [ "$reindexing_attempts" -eq 0 ]; then
36+
echo "Reindexing search..."
37+
else
38+
echo "Reindexing search... (attempt $reindexing_attempts)"
39+
fi
40+
41+
if trigger_reindex; then
42+
echo "...done reindexing"
43+
exit 0
44+
else
45+
echo "Reindex attempt failed"
46+
reindexing_attempts=$((reindexing_attempts + 1))
47+
if [ "$reindexing_attempts" -gt 30 ]; then
48+
echo "Failed to reindex search after $reindexing_attempts attempts."
49+
exit 1
50+
fi
51+
sleep 5
52+
fi
53+
done

infrastructure/postgres/setup-analytics.sh

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,31 +50,35 @@ PGPASSWORD="$POSTGRES_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$POSTGRES_HOST" -p "
5050
5151
CREATE SCHEMA IF NOT EXISTS analytics;
5252
53-
CREATE OR REPLACE VIEW analytics.locations
54-
WITH (security_barrier)
55-
AS
56-
SELECT * FROM app.locations;
53+
CREATE TABLE IF NOT EXISTS analytics.locations (
54+
id uuid PRIMARY KEY,
55+
name text NOT NULL,
56+
parent_id uuid REFERENCES analytics.locations(id),
57+
location_type TEXT NOT NULL
58+
);
59+
60+
CREATE UNIQUE INDEX IF NOT EXISTS analytics_locations_pkey ON analytics.locations(id uuid_ops);
5761
5862
CREATE TABLE IF NOT EXISTS analytics.event_actions (
5963
event_type text NOT NULL,
60-
action_type app.action_type NOT NULL,
64+
action_type TEXT NOT NULL,
6165
annotation jsonb,
6266
assigned_to text,
6367
created_at timestamp with time zone NOT NULL DEFAULT now(),
64-
created_at_location uuid REFERENCES app.locations(id),
68+
created_at_location uuid,
6569
created_by text NOT NULL,
6670
created_by_role text NOT NULL,
6771
created_by_signature text,
68-
created_by_user_type app.user_type NOT NULL,
72+
created_by_user_type TEXT NOT NULL,
6973
declared_at timestamp with time zone,
7074
registered_at timestamp with time zone,
7175
declaration jsonb NOT NULL DEFAULT '{}'::jsonb,
72-
event_id uuid NOT NULL REFERENCES app.events(id),
76+
event_id uuid NOT NULL,
7377
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
74-
original_action_id uuid REFERENCES app.event_actions(id),
78+
original_action_id uuid,
7579
registration_number text UNIQUE,
7680
request_id text,
77-
status app.action_status NOT NULL,
81+
status TEXT NOT NULL,
7882
transaction_id text NOT NULL,
7983
content jsonb,
8084
UNIQUE (id, event_id)

src/analytics/analytics.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import {
2424
EventDocument,
2525
EventState,
2626
getActionAnnotationFields,
27-
getCurrentEventState
27+
getCurrentEventState,
28+
Location
2829
} from '@opencrvs/toolkit/events'
2930
import { differenceInDays } from 'date-fns'
3031
import { ExpressionBuilder, Kysely } from 'kysely'
@@ -232,6 +233,43 @@ export async function importEvent(event: EventDocument, trx: Kysely<any>) {
232233
logger.info(`Event with id "${event.id}" logged into analytics`)
233234
}
234235

236+
export async function importLocations(locations: Location[]) {
237+
const client = getClient()
238+
await client.transaction().execute(async (trx) => {
239+
for (const [index, batch] of chunk(
240+
locations,
241+
INSERT_MAX_CHUNK_SIZE
242+
).entries()) {
243+
logger.info(
244+
`Importing ${Math.min((index + 1) * INSERT_MAX_CHUNK_SIZE, locations.length)}/${locations.length} locations`
245+
)
246+
247+
await trx
248+
.insertInto('analytics.locations')
249+
.values(
250+
batch.map((l) => ({
251+
id: l.id,
252+
name: l.name,
253+
parentId: l.parentId,
254+
locationType: l.locationType
255+
}))
256+
)
257+
.onConflict((oc) =>
258+
oc
259+
.column('id')
260+
.doUpdateSet(
261+
(eb: ExpressionBuilder<any, 'analytics.locations'>) => ({
262+
name: eb.ref('excluded.name'),
263+
parentId: eb.ref('excluded.parentId'),
264+
locationType: eb.ref('excluded.locationType')
265+
})
266+
)
267+
)
268+
.execute()
269+
}
270+
})
271+
}
272+
235273
export async function importEvents(events: EventDocument[], trx: Kysely<any>) {
236274
for (const event of events) {
237275
await importEvent(event, trx)

src/index.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import {
3030
COUNTRY_CONFIG_PORT,
3131
CHECK_INVALID_TOKEN,
3232
AUTH_URL,
33-
DEFAULT_TIMEOUT
33+
DEFAULT_TIMEOUT,
34+
GATEWAY_URL
3435
} from '@countryconfig/constants'
3536
import {
3637
contentHandler,
@@ -70,11 +71,13 @@ import getUserNotificationRoutes from './config/routes/userNotificationRoutes'
7071
import {
7172
importEvent,
7273
importEvents,
74+
importLocations,
7375
syncLocationLevels,
7476
syncLocationStatistics
7577
} from './analytics/analytics'
7678
import { getClient } from './analytics/postgres'
7779
import { env } from './environment'
80+
import { createClient } from '@opencrvs/toolkit/api'
7881

7982
export interface ITokenPayload {
8083
sub: string
@@ -605,6 +608,12 @@ export async function createServer() {
605608
if (queue.length > 0) {
606609
await importEvents(queue, trx)
607610
}
611+
612+
// Import locations
613+
const url = new URL('events', GATEWAY_URL).toString()
614+
const client = createClient(url, req.headers.authorization)
615+
const locations = await client.locations.list.query()
616+
await importLocations(locations)
608617
})
609618

610619
logger.info('Reindexed all events into analytics.')
@@ -687,6 +696,7 @@ export async function createServer() {
687696
const parsedPath = /^\/trigger\/events\/[^/]+\/actions\/([^/]+)$/.exec(
688697
request.route.path
689698
)
699+
690700
const actionType = parsedPath?.[1] as ActionType | null
691701
const wasRequestForActionConfirmation =
692702
actionType && request.method === 'post'
@@ -695,9 +705,15 @@ export async function createServer() {
695705
if (wasRequestForActionConfirmation && wasActionAcceptedImmediately) {
696706
const event = request.payload as EventDocument
697707
const client = getClient()
698-
await client.transaction().execute(async (trx) => {
699-
await importEvent(event, trx)
700-
})
708+
try {
709+
await client.transaction().execute(async (trx) => {
710+
await importEvent(event, trx)
711+
})
712+
} catch (error) {
713+
// eslint-disable-next-line no-console
714+
console.error(error)
715+
throw error
716+
}
701717
}
702718
return h.continue
703719
})

0 commit comments

Comments
 (0)