-
Notifications
You must be signed in to change notification settings - Fork 69
[OCRVS-10558] Fix reindexing output #1043
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
262886b
225b1f6
e4e5324
8a5ce75
f347da4
316f52e
9fbf9f7
4425d45
48555fb
b37c19b
b3dd637
4925a4c
810130f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| name: Reindex search | ||
| run-name: Reindex search in ${{ inputs.environment }} core=${{ inputs.core-image-tag }} | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| environment: | ||
| required: true | ||
| type: string | ||
| core-image-tag: | ||
| required: true | ||
| type: string | ||
| workflow_dispatch: | ||
| inputs: | ||
| environment: | ||
| type: choice | ||
| description: Environment where to reindex search | ||
| required: true | ||
| default: 'development' | ||
| options: | ||
| - development | ||
| - qa | ||
| - staging | ||
| - production | ||
| core-image-tag: | ||
| description: Core DockerHub image tag | ||
| required: true | ||
| jobs: | ||
| reindex: | ||
| environment: ${{ inputs.environment }} | ||
| runs-on: ubuntu-24.04 | ||
| timeout-minutes: 60 | ||
|
|
||
| steps: | ||
| - name: Clone country config resource package | ||
| uses: actions/checkout@v3 | ||
| with: | ||
| fetch-depth: 0 | ||
| ref: ${{ github.ref_name }} | ||
| path: './${{ github.event.repository.name }}' | ||
|
|
||
| - name: Read known hosts | ||
| run: | | ||
| cd ${{ github.event.repository.name }} | ||
| echo "KNOWN_HOSTS<<EOF" >> $GITHUB_ENV | ||
| sed -i -e '$a\' ./infrastructure/known-hosts | ||
| cat ./infrastructure/known-hosts >> $GITHUB_ENV | ||
| echo "EOF" >> $GITHUB_ENV | ||
| - name: Install SSH Key | ||
| uses: shimataro/ssh-key-action@v2 | ||
| with: | ||
| key: ${{ secrets.SSH_KEY }} | ||
| known_hosts: ${{ env.KNOWN_HOSTS }} | ||
|
|
||
| - name: Run reindex script in docker container | ||
| run: | | ||
| ssh -p ${{ vars.SSH_PORT }} ${{ secrets.SSH_USER }}@${{ vars.SSH_HOST }} ${{ vars.SSH_ARGS }} " | ||
| docker run --rm \ | ||
| -v /opt/opencrvs/infrastructure/deployment:/workspace \ | ||
| -w /workspace \ | ||
| --network opencrvs_overlay_net \ | ||
| -e 'AUTH_URL=http://auth:4040/' \ | ||
| -e 'EVENTS_URL=http://gateway:7070/events/reindex' \ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Reindex Workflow SSH and URL Configuration IssuesThe reindex workflow's SSH connection uses |
||
| alpine \ | ||
| sh -c 'apk add --no-cache curl jq && sh reindex.sh'" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # This Source Code Form is subject to the terms of the Mozilla Public | ||
| # License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| # file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
| # | ||
| # OpenCRVS is also distributed under the terms of the Civil Registration | ||
| # & Healthcare Disclaimer located at http://opencrvs.org/license. | ||
| # | ||
| # Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. | ||
|
|
||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| EVENTS_URL="${EVENTS_URL:-http://localhost:5555/}" | ||
| AUTH_URL="${AUTH_URL:-http://localhost:4040/}" | ||
|
|
||
|
|
||
| get_reindexing_token() { | ||
| curl -s "${AUTH_URL%/}/internal/reindexing-token" | jq -r '.token' | ||
| } | ||
|
|
||
| trigger_reindex() { | ||
| token="$(get_reindexing_token)" | ||
| out="$(curl -s -w '\n%{http_code}' -X POST \ | ||
| -H "Authorization: Bearer ${token}" \ | ||
| -H "Content-Type: application/json" \ | ||
| "${EVENTS_URL%/}/events/reindex")" | ||
| body="$(printf '%s' "$out" | sed '$d')" | ||
| code="$(printf '%s' "$out" | tail -n1)" | ||
| echo "$body" | ||
| [ "$code" -ge 200 ] && [ "$code" -lt 300 ] | ||
| } | ||
|
|
||
| reindexing_attempts=0 | ||
| while true; do | ||
| if [ "$reindexing_attempts" -eq 0 ]; then | ||
| echo "Reindexing search..." | ||
| else | ||
| echo "Reindexing search... (attempt $reindexing_attempts)" | ||
| fi | ||
|
|
||
| if trigger_reindex; then | ||
| echo "...done reindexing" | ||
| exit 0 | ||
| else | ||
| echo "Reindex attempt failed" | ||
| reindexing_attempts=$((reindexing_attempts + 1)) | ||
| if [ "$reindexing_attempts" -gt 30 ]; then | ||
| echo "Failed to reindex search after $reindexing_attempts attempts." | ||
| exit 1 | ||
| fi | ||
| sleep 5 | ||
| fi | ||
| done |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,31 +50,35 @@ PGPASSWORD="$POSTGRES_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$POSTGRES_HOST" -p " | |
|
|
||
| CREATE SCHEMA IF NOT EXISTS analytics; | ||
|
|
||
| CREATE OR REPLACE VIEW analytics.locations | ||
| WITH (security_barrier) | ||
| AS | ||
| SELECT * FROM app.locations; | ||
| CREATE TABLE IF NOT EXISTS analytics.locations ( | ||
| id uuid PRIMARY KEY, | ||
| name text NOT NULL, | ||
| parent_id uuid REFERENCES analytics.locations(id), | ||
| location_type TEXT NOT NULL | ||
| ); | ||
|
|
||
| CREATE UNIQUE INDEX IF NOT EXISTS analytics_locations_pkey ON analytics.locations(id uuid_ops); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| CREATE TABLE IF NOT EXISTS analytics.event_actions ( | ||
| event_type text NOT NULL, | ||
| action_type app.action_type NOT NULL, | ||
| action_type TEXT NOT NULL, | ||
| annotation jsonb, | ||
| assigned_to text, | ||
| created_at timestamp with time zone NOT NULL DEFAULT now(), | ||
| created_at_location uuid REFERENCES app.locations(id), | ||
| created_at_location uuid, | ||
| created_by text NOT NULL, | ||
| created_by_role text NOT NULL, | ||
| created_by_signature text, | ||
| created_by_user_type app.user_type NOT NULL, | ||
| created_by_user_type TEXT NOT NULL, | ||
| declared_at timestamp with time zone, | ||
| registered_at timestamp with time zone, | ||
| declaration jsonb NOT NULL DEFAULT '{}'::jsonb, | ||
| event_id uuid NOT NULL REFERENCES app.events(id), | ||
| event_id uuid NOT NULL, | ||
| id uuid DEFAULT gen_random_uuid() PRIMARY KEY, | ||
| original_action_id uuid REFERENCES app.event_actions(id), | ||
| original_action_id uuid, | ||
| registration_number text UNIQUE, | ||
| request_id text, | ||
| status app.action_status NOT NULL, | ||
| status TEXT NOT NULL, | ||
| transaction_id text NOT NULL, | ||
| content jsonb, | ||
| UNIQUE (id, event_id) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,8 @@ import { | |
| EventDocument, | ||
| EventState, | ||
| getActionAnnotationFields, | ||
| getCurrentEventState | ||
| getCurrentEventState, | ||
| Location | ||
| } from '@opencrvs/toolkit/events' | ||
| import { differenceInDays } from 'date-fns' | ||
| import { ExpressionBuilder, Kysely } from 'kysely' | ||
|
|
@@ -232,6 +233,43 @@ export async function importEvent(event: EventDocument, trx: Kysely<any>) { | |
| logger.info(`Event with id "${event.id}" logged into analytics`) | ||
| } | ||
|
|
||
| export async function importLocations(locations: Location[]) { | ||
| const client = getClient() | ||
| await client.transaction().execute(async (trx) => { | ||
| for (const [index, batch] of chunk( | ||
| locations, | ||
| INSERT_MAX_CHUNK_SIZE | ||
| ).entries()) { | ||
| logger.info( | ||
| `Importing ${Math.min((index + 1) * INSERT_MAX_CHUNK_SIZE, locations.length)}/${locations.length} locations` | ||
| ) | ||
|
|
||
| await trx | ||
| .insertInto('analytics.locations') | ||
| .values( | ||
| batch.map((l) => ({ | ||
| id: l.id, | ||
| name: l.name, | ||
| parentId: l.parentId, | ||
| locationType: l.locationType | ||
| })) | ||
| ) | ||
| .onConflict((oc) => | ||
| oc | ||
| .column('id') | ||
| .doUpdateSet( | ||
| (eb: ExpressionBuilder<any, 'analytics.locations'>) => ({ | ||
| name: eb.ref('excluded.name'), | ||
| parentId: eb.ref('excluded.parentId'), | ||
| locationType: eb.ref('excluded.locationType') | ||
| }) | ||
| ) | ||
| ) | ||
| .execute() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| }) | ||
| } | ||
|
|
||
| export async function importEvents(events: EventDocument[], trx: Kysely<any>) { | ||
| for (const event of events) { | ||
| await importEvent(event, trx) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,7 +30,8 @@ import { | |
| COUNTRY_CONFIG_PORT, | ||
| CHECK_INVALID_TOKEN, | ||
| AUTH_URL, | ||
| DEFAULT_TIMEOUT | ||
| DEFAULT_TIMEOUT, | ||
| GATEWAY_URL | ||
| } from '@countryconfig/constants' | ||
| import { | ||
| contentHandler, | ||
|
|
@@ -70,11 +71,13 @@ import getUserNotificationRoutes from './config/routes/userNotificationRoutes' | |
| import { | ||
| importEvent, | ||
| importEvents, | ||
| importLocations, | ||
| syncLocationLevels, | ||
| syncLocationStatistics | ||
| } from './analytics/analytics' | ||
| import { getClient } from './analytics/postgres' | ||
| import { env } from './environment' | ||
| import { createClient } from '@opencrvs/toolkit/api' | ||
|
|
||
| export interface ITokenPayload { | ||
| sub: string | ||
|
|
@@ -605,6 +608,12 @@ export async function createServer() { | |
| if (queue.length > 0) { | ||
| await importEvents(queue, trx) | ||
| } | ||
|
|
||
| // Import locations | ||
| const url = new URL('events', GATEWAY_URL).toString() | ||
| const client = createClient(url, req.headers.authorization) | ||
| const locations = await client.locations.list.query() | ||
| await importLocations(locations) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: Transaction Inconsistency in Import LocationsThe |
||
| }) | ||
|
|
||
| logger.info('Reindexed all events into analytics.') | ||
|
|
@@ -687,6 +696,7 @@ export async function createServer() { | |
| const parsedPath = /^\/trigger\/events\/[^/]+\/actions\/([^/]+)$/.exec( | ||
| request.route.path | ||
| ) | ||
|
|
||
| const actionType = parsedPath?.[1] as ActionType | null | ||
| const wasRequestForActionConfirmation = | ||
| actionType && request.method === 'post' | ||
|
|
@@ -695,9 +705,15 @@ export async function createServer() { | |
| if (wasRequestForActionConfirmation && wasActionAcceptedImmediately) { | ||
| const event = request.payload as EventDocument | ||
| const client = getClient() | ||
| await client.transaction().execute(async (trx) => { | ||
| await importEvent(event, trx) | ||
| }) | ||
| try { | ||
| await client.transaction().execute(async (trx) => { | ||
| await importEvent(event, trx) | ||
| }) | ||
| } catch (error) { | ||
| // eslint-disable-next-line no-console | ||
| console.error(error) | ||
| throw error | ||
| } | ||
| } | ||
| return h.continue | ||
| }) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Inconsistent Job Success Checks
The new reindex jobs check for success using
needs.<job>.result, which is inconsistent with the established pattern of usingneeds.<job>.outputs.outcomein this workflow. For jobs likedeploythat explicitly define anoutcomeoutput,resultandoutputs.outcomecan differ, potentially causing reindex jobs to run unexpectedly.