Skip to content

Commit c78dd6f

Browse files
committed
Add decode-s3-paths plugin
Plugin to decode base64-encoded fileuri parameters in resolve URLs, displaying human-readable S3/GCS/Azure paths in Text tags. Useful when displaying cloud storage paths for reference during image quality control or data validation tasks.
1 parent 28cc9cc commit c78dd6f

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed

manifest.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,11 @@
8282
"description": "Dynamically populates a text box given the input of a text area tag for span based labeling",
8383
"path": "dynamic-text-spans",
8484
"private": false
85+
},
86+
{
87+
"title": "Decode S3/cloud storage paths",
88+
"description": "Decodes base64-encoded fileuri parameters in resolve URLs to show human-readable S3/GCS/Azure paths",
89+
"path": "decode-s3-paths",
90+
"private": false
8591
}
8692
]

src/decode-s3-paths/data.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"data": {
3+
"image": "/tasks/12345/resolve/?fileuri=czM6Ly9teS1idWNrZXQvaW1hZ2VzL3NhbXBsZS5qcGc="
4+
}
5+
}

src/decode-s3-paths/plugin.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* Plugin: Decode S3 paths from base64 fileuri parameters
3+
*
4+
* When using cloud storage (S3, GCS, Azure), Label Studio generates resolve URLs
5+
* with base64-encoded file paths. This plugin decodes those paths to show
6+
* human-readable S3/GCS/Azure paths in Text tags.
7+
*
8+
* Example:
9+
* Before: /tasks/123/resolve/?fileuri=czM6Ly9idWNrZXQvcGF0aC9maWxlLmpwZw==
10+
* After: s3://bucket/path/file.jpg
11+
*/
12+
(() => {
13+
// Cache for already processed elements (avoid re-processing)
14+
const processedElements = new WeakSet();
15+
16+
// Debounce timer
17+
let debounceTimer = null;
18+
19+
/**
20+
* Decode base64 fileuri to S3/GCS/Azure path
21+
* @param {string} text - Text containing fileuri parameter
22+
* @returns {string|null} - Decoded path or null
23+
*/
24+
function decodeBase64Path(text) {
25+
const match = text.match(/fileuri=([A-Za-z0-9+/=_-]+)/);
26+
if (!match) return null;
27+
28+
try {
29+
// Handle URL-safe base64 variants (replace - with + and _ with /)
30+
const b64 = match[1].replace(/-/g, "+").replace(/_/g, "/");
31+
return atob(b64);
32+
} catch (e) {
33+
console.warn("[S3 Path Decoder] Failed to decode:", e.message);
34+
return null;
35+
}
36+
}
37+
38+
/**
39+
* Process text nodes and replace encoded paths with decoded ones
40+
*/
41+
function processTextNodes() {
42+
// Target elements likely containing path text
43+
const candidates = document.querySelectorAll('[class*="text"], [class*="Text"], span, p, div');
44+
45+
for (const el of candidates) {
46+
// Skip already processed elements
47+
if (processedElements.has(el)) continue;
48+
49+
// Only process leaf text nodes (elements with single text child)
50+
if (el.childNodes.length !== 1 || el.childNodes[0].nodeType !== 3) continue;
51+
52+
const text = el.textContent;
53+
if (!text || !text.includes("/resolve/?fileuri=")) continue;
54+
55+
const decoded = decodeBase64Path(text);
56+
if (decoded) {
57+
el.textContent = text.replace(/\/tasks\/\d+\/resolve\/\?fileuri=[A-Za-z0-9+/=_-]+/, decoded);
58+
processedElements.add(el);
59+
}
60+
}
61+
}
62+
63+
/**
64+
* Debounced processing to avoid excessive DOM operations
65+
*/
66+
function debouncedProcess() {
67+
if (debounceTimer) clearTimeout(debounceTimer);
68+
debounceTimer = setTimeout(processTextNodes, 150);
69+
}
70+
71+
/**
72+
* Initialize the plugin
73+
*/
74+
function init() {
75+
// Initial processing with staggered retries for dynamic content
76+
processTextNodes();
77+
setTimeout(processTextNodes, 500);
78+
setTimeout(processTextNodes, 1500);
79+
80+
// Watch for dynamic content changes (task navigation, etc.)
81+
const observer = new MutationObserver(debouncedProcess);
82+
83+
observer.observe(document.body, {
84+
childList: true,
85+
subtree: true,
86+
});
87+
88+
// Cleanup on page unload
89+
window.addEventListener("beforeunload", () => {
90+
observer.disconnect();
91+
if (debounceTimer) clearTimeout(debounceTimer);
92+
});
93+
}
94+
95+
// Start when DOM is ready
96+
if (document.readyState === "loading") {
97+
document.addEventListener("DOMContentLoaded", init);
98+
} else {
99+
init();
100+
}
101+
})();

src/decode-s3-paths/view.xml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<View>
2+
<Header value="Image Quality Review"/>
3+
4+
<!-- Display decoded S3 path for reference -->
5+
<View style="padding: 10px; background-color: #f5f5f5; border-radius: 4px; margin-bottom: 15px;">
6+
<Text name="image_path" value="Path: $image"/>
7+
</View>
8+
9+
<!-- The image to review -->
10+
<Image name="img" value="$image" zoom="true"/>
11+
12+
<!-- Simple quality assessment -->
13+
<View style="margin-top: 15px;">
14+
<Header value="Is this image acceptable?"/>
15+
<Choices name="quality" toName="img" choice="single" required="true">
16+
<Choice value="Yes"/>
17+
<Choice value="No"/>
18+
</Choices>
19+
</View>
20+
21+
<TextArea name="notes" toName="img"
22+
placeholder="Optional notes..."
23+
rows="2" maxSubmissions="1"/>
24+
</View>

0 commit comments

Comments
 (0)