Skip to content

Commit

Permalink
Ready to test
Browse files Browse the repository at this point in the history
  • Loading branch information
kennsippell committed Nov 26, 2024
1 parent 767bbe4 commit 4bd29ef
Show file tree
Hide file tree
Showing 12 changed files with 285 additions and 160 deletions.
2 changes: 1 addition & 1 deletion src/config/config-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const WorkerConfig = {
host: environment.REDIS_HOST,
port: Number(environment.REDIS_PORT),
},
moveContactQueue: 'MOVE_CONTACT_QUEUE',
queueName: 'MOVE_CONTACT_QUEUE',
defaultJobOptions: {
attempts: 3, // Max retries for a failed job
backoff: {
Expand Down
137 changes: 100 additions & 37 deletions src/lib/manage-hierarchy.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,129 @@
import { ContactType } from '../config';
import SessionCache from '../services/session-cache';
import { ChtApi } from './cht-api';
import { ChtApi, RemotePlace } from './cht-api';
import RemotePlaceResolver from './remote-place-resolver';
import Place from '../services/place';

import { JobParams, IQueue, getMoveContactQueue } from './queues';
import { JobParams, IQueue, getChtConfQueue } from './queues';
import Auth from './authentication';
import { ChtConfJobData } from '../worker/cht-conf-worker';
import _ from 'lodash';

export const HIERARCHY_ACTIONS = ['move', 'merge', 'delete'];
export type HierarchyAction = typeof HIERARCHY_ACTIONS[number];

export default class ManageHierarchyLib {
constructor() { }
private constructor() { }

public static async move(
formData: any, contactType: ContactType, sessionCache: SessionCache, chtApi: ChtApi, moveContactQueue: IQueue = getMoveContactQueue()
public static async scheduleJob(
formData: any,
contactType: ContactType,
sessionCache: SessionCache,
chtApi: ChtApi,
queueName: IQueue = getChtConfQueue()
) {
const fromLineage = await resolve('from_', formData, contactType, sessionCache, chtApi);
const toLineage = await resolve('to_', formData, contactType, sessionCache, chtApi);
const { sourceLineage, destinationLineage, jobParam } = await getJobDetails(formData, contactType, sessionCache, chtApi);

const toId = toLineage[1]?.id;
const fromId = fromLineage[0]?.id;
if (!toId || !fromId) {
throw Error('Unexpected error: Move failed');
}

if (toId === fromLineage[1]?.id) {
throw Error(`Place "${fromLineage[0]?.name}" already has "${toLineage[1]?.name}" as parent`);
}

const jobData = this.getJobData(fromId, toId, chtApi);
const jobName = this.getJobName(fromLineage[0]?.name, fromLineage[1]?.name, toLineage[1]?.name);
const jobParam: JobParams = {
jobName,
jobData,
};
await moveContactQueue.add(jobParam);
await queueName.add(jobParam);

return {
toLineage,
fromLineage,
destinationLineage,
sourceLineage,
success: true
};
}

private static getJobName(sourceChpName?: string, sourceChuName?: string, destinationChuName?: string): string {
return `move_[${sourceChpName}]_from_[${sourceChuName}]_to_[${destinationChuName}]`;
public static parseHierarchyAction(action: string = ''): HierarchyAction {
if (!HIERARCHY_ACTIONS.includes(action)) {
throw Error(`invalid action: "${action}"`);
}

return action as HierarchyAction;
}
}

private static getJobData(sourceId: string, destinationId: string, chtApi: ChtApi): ChtConfJobData {
const { authInfo } = chtApi.chtSession;
return {
instanceUrl: `http${authInfo.useHttp ? '' : 's'}://${authInfo.domain}`,
sessionToken: Auth.encodeTokenForWorker(chtApi.chtSession),
action: 'move',
sourceId,
destinationId,
};
async function getJobDetails(formData: any, contactType: ContactType, sessionCache: SessionCache, chtApi: ChtApi) {
const hierarchyAction = ManageHierarchyLib.parseHierarchyAction(formData.op);
const sourceLineage = await resolve('source_', formData, contactType, sessionCache, chtApi);
const destinationLineage = hierarchyAction === 'delete' ? [] : await resolve('destination_', formData, contactType, sessionCache, chtApi);

const { sourceId, destinationId } = getSourceAndDestination();
const jobData = getJobData(hierarchyAction, sourceId, destinationId, chtApi);
const jobName = getJobName(jobData.action, sourceLineage, destinationLineage);
const jobParam: JobParams = {
jobName,
jobData,
};

return {
sourceLineage,
destinationLineage,
jobParam
};

function getSourceAndDestination() {
if (hierarchyAction === 'move') {
const sourceId = sourceLineage[0]?.id;
const destinationId = destinationLineage[1]?.id;
if (!destinationId || !sourceId) {
throw Error('Unexpected error: Move failed due to missing information');
}

if (destinationId === sourceLineage[1]?.id) {
throw Error(`Place "${sourceLineage[0]?.name}" already has "${destinationLineage[1]?.name}" as parent`);
}

return { sourceId, destinationId };
}

if (hierarchyAction === 'merge') {
const sourceId = sourceLineage[0]?.id;
const destinationId = destinationLineage[0]?.id;
if (!destinationId || !sourceId) {
throw Error('Unexpected error: Merge failed due to missing information');
}

if (destinationId === sourceId) {
throw Error(`Cannot merge "${destinationId}" with self`);
}

return { sourceId, destinationId };
}

const sourceId = sourceLineage[0]?.id;
if (!sourceId) {
throw Error('Unexpected error: Delete failed due to missing information');
}

return { sourceId, destinationId: '' };
}
}

function getJobName(action: string, sourceLineage: (RemotePlace | undefined)[], destinationLineage: (RemotePlace | undefined)[]): string {
const sourceDescription = describeLineage(sourceLineage);
const destinationDescription = describeLineage(destinationLineage);
const formattedDestinationDescription = destinationDescription && `_to_[${destinationDescription}]`;
return `${action}_[${sourceDescription}]${formattedDestinationDescription}`;

function describeLineage(lineage: (RemotePlace | undefined)[]) : string | undefined {
return _.reverse([...lineage])
.map(element => element?.name)
.filter(Boolean)
.join('.');
}
}

function getJobData(action: HierarchyAction, sourceId: string, destinationId: string, chtApi: ChtApi): ChtConfJobData {
const { authInfo } = chtApi.chtSession;
return {
action,
instanceUrl: `http${authInfo.useHttp ? '' : 's'}://${authInfo.domain}`,
sessionToken: Auth.encodeTokenForWorker(chtApi.chtSession),
sourceId,
destinationId,
};
}

async function resolve(prefix: string, formData: any, contactType: ContactType, sessionCache: SessionCache, chtApi: ChtApi) {
const place = new Place(contactType);
place.setPropertiesFromFormData(formData, prefix);
Expand Down
4 changes: 2 additions & 2 deletions src/lib/queues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export class BullQueue implements IQueue {
}
}

export const getMoveContactQueue = () => new BullQueue(
WorkerConfig.moveContactQueue,
export const getChtConfQueue = () => new BullQueue(
WorkerConfig.queueName,
WorkerConfig.redisConnection,
WorkerConfig.defaultJobOptions
);
4 changes: 2 additions & 2 deletions src/liquid/place/manage_hierarchy_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ <h1 class="subtitle">{{ sourceDescription }}</h1>
hierarchy=hierarchy
data=data
required=hierarchy.required
prefix="from_"
prefix="source_"
%}
{% endfor %}
</section>
Expand All @@ -43,7 +43,7 @@ <h1 class="subtitle">{{ destinationDescription }}</h1>
hierarchy=hierarchy
data=data
required=hierarchy.required
prefix="to_"
prefix="destination_"
%}
{% endfor %}
</section>
Expand Down
4 changes: 2 additions & 2 deletions src/plugins/bullmq.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { createBullBoard } from '@bull-board/api';
import { FastifyAdapter } from '@bull-board/fastify';

import { getMoveContactQueue } from '../lib/queues';
import { getChtConfQueue } from '../lib/queues';


async function bullMQBoardPlugin(fastify: FastifyInstance) {
Expand All @@ -15,7 +15,7 @@ async function bullMQBoardPlugin(fastify: FastifyInstance) {
createBullBoard({
queues: [
new BullMQAdapter(
getMoveContactQueue().bullQueue
getChtConfQueue().bullQueue
),
],
serverAdapter,
Expand Down
2 changes: 1 addition & 1 deletion src/routes/manage-hierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default async function sessionCache(fastify: FastifyInstance) {
const chtApi = new ChtApi(req.chtSession);

try {
const result = await ManageHierarchyLib.move(formData, contactType, sessionCache, chtApi);
const result = await ManageHierarchyLib.scheduleJob(formData, contactType, sessionCache, chtApi);

const tmplData = {
view: 'manage-hierarchy',
Expand Down
12 changes: 2 additions & 10 deletions src/services/hierarchy-view-model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _ from 'lodash';
import { Config, ContactType } from '../config';
import { HIERARCHY_ACTIONS, HierarchyAction } from '../lib/manage-hierarchy';
import ManageHierarchyLib, { HIERARCHY_ACTIONS, HierarchyAction } from '../lib/manage-hierarchy';

export function hierarchyViewModel(action: string, contactType: ContactType) {
const parentTypeName = contactType.hierarchy.find(h => h.level === 1)?.contact_type;
Expand All @@ -10,7 +10,7 @@ export function hierarchyViewModel(action: string, contactType: ContactType) {

const sourceHierarchy = Config.getHierarchyWithReplacement(contactType, 'desc');
sourceHierarchy[sourceHierarchy.length - 1].friendly_name = contactType.friendly;
const hierarchyAction = getAction(action);
const hierarchyAction = ManageHierarchyLib.parseHierarchyAction(action);
const destinationHierarchy = getDestinationHierarchy();
const sourceDescription = hierarchyAction === 'move' ? 'Move This Contact' : 'Delete This Contact';
const destinationDescription = hierarchyAction === 'move' ? 'To Have This Parent' : 'After Moving Data Into';
Expand All @@ -34,12 +34,4 @@ export function hierarchyViewModel(action: string, contactType: ContactType) {

return _.orderBy(contactType.hierarchy, 'level', 'desc');
}

function getAction(action: string = ''): HierarchyAction {
if (!HIERARCHY_ACTIONS.includes(action)) {
throw Error(`invalid action: "${action}"`);
}

return action as HierarchyAction;
}
}
3 changes: 2 additions & 1 deletion src/worker/cht-conf-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { Worker, Job, DelayedError, ConnectionOptions, MinimalJob } from 'bullmq
import { DateTime } from 'luxon';

import Auth from '../lib/authentication';
import { HierarchyAction } from '../lib/manage-hierarchy';

export interface ChtConfJobData {
sourceId: string;
destinationId: string;
action: 'move' | 'merge' | 'delete';
action: HierarchyAction;
sessionToken: string;
instanceUrl: string;
}
Expand Down
4 changes: 2 additions & 2 deletions src/worker/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import { ChtConfWorker } from './cht-conf-worker';
import { WorkerConfig, checkRedisConnection } from '../config/config-worker';

(async () => {
const { moveContactQueue, redisConnection} = WorkerConfig;
const { queueName, redisConnection} = WorkerConfig;
await checkRedisConnection();
ChtConfWorker.processQueue(
moveContactQueue,
queueName,
redisConnection
);
console.log(`🚀 CHT Conf Worker is listening`);
Expand Down
4 changes: 2 additions & 2 deletions test/integration/manage-hierarchy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('integration/manage-hierarchy', function () {
encodeTokenStub.returns('encoded-token');
decodeTokenStub.returns(session);

await MoveLib.move(
await MoveLib.scheduleJob(
formData, contactType, sessionCache, chtApi(), moveContactQueue
);

Expand Down Expand Up @@ -107,7 +107,7 @@ describe('integration/manage-hierarchy', function () {
encodeTokenStub.returns('encoded-token');
decodeTokenStub.throws(new Error('Missing WORKER_PRIVATE_KEY'));

await MoveLib.move(
await MoveLib.scheduleJob(
formData, contactType, sessionCache, chtApi(), moveContactQueue
);

Expand Down
Loading

0 comments on commit 4bd29ef

Please sign in to comment.