feat: show annotations from job to user#172
Conversation
FilipVrubel
commented
May 19, 2026
- After a job completes, its output annotations are fetched and drawn on a Canvas2D overlay on the slide (read-only, non-interactive)
- _captureAnnotation now posts directly to MDS via scope.annotations.create() instead of polling for an ID
- string inputs render as textarea
- Known limitation: overlay is read-only, not part of the OSDAnnotations system.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a JobResultsOverlay class to render job results on the OpenSeadragon viewer and updates the analyze-dev plugin to fetch, decode, and display these results upon job completion. It also refactors the annotation capture logic to create annotations directly in the MDS and adds support for multi-line string inputs. The review feedback suggests optimizing performance by parallelizing annotation fetching, improving code readability with declarative array methods, and refactoring the shape-drawing logic into a strategy pattern for better extensibility.
| const allShapes = []; | ||
| for (const key of annotationKeys) { | ||
| const collectionId = job.outputs[key]; | ||
| if (!collectionId) { | ||
| console.log('[analyze] no collection ID for output key', key); | ||
| continue; | ||
| } | ||
| try { | ||
| const result = await scope.collections.queryItems(collectionId, {}); | ||
| if (!result?.items?.length) { | ||
| console.log('[analyze] empty collection for key', key); | ||
| continue; | ||
| } | ||
| console.log('[analyze] fetched', result.items.length, 'annotations for key', key); | ||
| const decoded = await this._empaiaConvertor.decode({ items: result.items, presets: [] }); | ||
| if (decoded?.objects) allShapes.push(...decoded.objects.filter(Boolean)); | ||
| } catch (e) { | ||
| console.warn('[analyze] failed to fetch/decode annotations for key', key, e); | ||
| } | ||
| } |
There was a problem hiding this comment.
The loop for fetching and decoding annotations runs sequentially for each annotation key. Since these are independent network requests, they can be parallelized using Promise.all to improve performance, especially when there are multiple annotation outputs.
const promises = annotationKeys.map(async (key) => {
const collectionId = job.outputs[key];
if (!collectionId) {
console.log('[analyze] no collection ID for output key', key);
return [];
}
try {
const result = await scope.collections.queryItems(collectionId, {});
if (!result?.items?.length) {
console.log('[analyze] empty collection for key', key);
return [];
}
console.log('[analyze] fetched', result.items.length, 'annotations for key', key);
const decoded = await this._empaiaConvertor.decode({ items: result.items, presets: [] });
return decoded?.objects?.filter(Boolean) || [];
} catch (e) {
console.warn('[analyze] failed to fetch/decode annotations for key', key, e);
return [];
}
});
const allShapes = (await Promise.all(promises)).flat();| state.objects = []; | ||
| for (const [, job] of this._jobStore) { | ||
| if (String(job.viewerId) !== String(viewerId)) continue; | ||
| for (const obj of job.fabricObjects) state.objects.push(obj); | ||
| } |
There was a problem hiding this comment.
| return tiledImage.imageToViewerElementCoordinates(new OpenSeadragon.Point(x, y)); | ||
| } | ||
|
|
||
| _drawShape(ctx, decoded, color, tiledImage) { |
There was a problem hiding this comment.
The _drawShape method uses a long if/else if chain to handle different shape types. For better maintainability and extensibility, consider refactoring this into a strategy pattern, for example by using a map of factoryID to drawing functions. Each drawing function could be a separate private method on the class.
|
The annotation integration should rely either on annotations module api, or use the flex renderer tile sources to render annotations as overlays. Not implementing new custom approach. |