Skip to content

Commit

Permalink
fix: OPTIC-1353: Make HeidiTips live for auth pages (HumanSignal#6665)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmartel authored Nov 19, 2024
1 parent 53c3cf9 commit f86f208
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 52 deletions.
182 changes: 131 additions & 51 deletions label_studio/users/templates/users/new-ui/user_tips.html
Original file line number Diff line number Diff line change
@@ -1,58 +1,69 @@
{% load filters %}

<script nonce="{{request.csp_nonce}}">
const dataTips = [
{
title: 'Did you know?',
description: 'You can sync data from all popular cloud storage providers to collect new items for labeling as they are uploaded, and return the annotation results to train and continuously improve models.',
cta: {
text: 'Learn more',
url: `https://labelstud.io/guide/storage`,
params: {
experiment: 'login_revamp',
treatment: 'sync_cloud_data',
}
},

}, {
title: 'Did you know?',
description: 'You can enable webhooks to trigger machine learning model training or perform active learning after a certain number of tasks have been annotated.',
cta: {
text: 'See use cases',
url: 'https://labelstud.io/guide/webhooks#What-to-use-Label-Studio-webhooks-for',
params: {
experiment: 'login_revamp',
treatment: 'enable_webhooks',
}
},
const SERVER_ID = {{ request.server_id|json_dumps_ensure_ascii|safe }};
const CACHE_KEY = "heidi_live_tips_collection";
const CACHE_FETCHED_AT_KEY = "heidi_live_tips_collection_fetched_at";
const CACHE_STALE_TIME = 1000 * 60 * 60; // 1 hour
const MAX_TIMEOUT = 5000; // 5 seconds

}, {
title: 'Did you know?',
description: "There's an Enterprise version of Label Studio packed with more features and automation to label data faster while ensuring the highest quality.",
cta: {
text: 'Learn more',
url: 'https://humansignal.com/goenterprise/',
params: {
experiment: 'login_revamp',
treatment: 'enterprise_platform',
}
const defaultTipsCollection = {
authPage: [
{
title: "Did you know?",
description:
"You can sync data from all popular cloud storage providers to collect new items for labeling as they are uploaded, and return the annotation results to train and continuously improve models.",
link: {
label: "Learn more",
url: "https://labelstud.io/guide/storage",
params: {
experiment: "login_revamp",
treatment: "sync_cloud_data",
},
},
},

}, {
title: 'Did you know?',
description: 'Label Studio has dozens of pre-built templates for all data types you can use to configure your labeling UI, from image classification to sentiment analysis to supervised LLM fine-tuning. ',
cta: {
text: 'See all templates',
url: 'https://labelstud.io/templates',
params: {
experiment: 'login_revamp',
treatment: 'templates',
}
{
title: "Did you know?",
description:
"You can enable webhooks to trigger machine learning model training or perform active learning after a certain number of tasks have been annotated.",
link: {
label: "See use cases",
url: "https://labelstud.io/guide/webhooks#What-to-use-Label-Studio-webhooks-for",
params: {
experiment: "login_revamp",
treatment: "enable_webhooks",
},
},
},
}
];

const SERVER_ID = {{ request.server_id|json_dumps_ensure_ascii|safe }};
{
title: "Did you know?",
description:
"There's an Enterprise version of Label Studio packed with more features and automation to label data faster while ensuring the highest quality.",
link: {
label: "Learn more",
url: "https://humansignal.com/goenterprise/",
params: {
experiment: "login_revamp",
treatment: "enterprise_platform",
},
},
},
{
title: "Did you know?",
description:
"Label Studio has dozens of pre-built templates for all data types you can use to configure your labeling UI, from image classification to sentiment analysis to supervised LLM fine-tuning. ",
link: {
label: "See all templates",
url: "https://labelstud.io/templates",
params: {
experiment: "login_revamp",
treatment: "templates",
},
},
},
],
};

function createURL(url, params) {
const base = new URL(url);
Expand All @@ -66,15 +77,84 @@
return base.toString()
}

const loadLiveTipsCollection = () => {
// stale while revalidate - we will return the data present in the cache or the default data and fetch updated data to be put into the cache for the next time this function is called without waiting for the promise.
const cachedData = localStorage.getItem(CACHE_KEY);
const fetchedAt = localStorage.getItem(CACHE_FETCHED_AT_KEY);

// Read from local storage if the cachedData is less than CACHE_STALE_TIME milliseconds old
if (cachedData && fetchedAt && Date.now() - Number.parseInt(fetchedAt) < CACHE_STALE_TIME) {
return JSON.parse(cachedData);
}

const abortController = new AbortController();

// Abort the request after MAX_TIMEOUT milliseconds to ensure we won't wait for too long, something might be wrong with the network or it could be an air-gapped instance
const abortTimeout = setTimeout(abortController.abort, MAX_TIMEOUT);

// Fetch from github raw liveContent.json proxied through the server
fetch("/heidi-tips", {
headers: {
"Cache-Control": "no-cache",
"Content-Type": "application/json",
},
signal: abortController.signal,
})
.then(async (response) => {
if (response.ok) {
const data = await response.json();

// Cache the fetched content
localStorage.setItem(CACHE_FETCHED_AT_KEY, String(Date.now()));
localStorage.setItem(CACHE_KEY, JSON.stringify(data));
}
})
.catch((e) => {
console.warn("Failed to load live Heidi tips collection", e);
})
.finally(() => {
// Wait until the content is fetched to clear the abort timeout
// The abort should consider the entire request not just the headers
clearTimeout(abortTimeout);
});

// Serve possibly stale cached content
if (cachedData) {
return JSON.parse(cachedData);
}

// Default local content
return defaultTipsCollection;
};

function getRandomTip() {
const tipsCollection = loadLiveTipsCollection();

if (!tipsCollection['authPage']) return null;

const tips = tipsCollection['authPage'];

const index = Math.floor(Math.random() * tips.length);

return tips[index];
}

document.addEventListener('DOMContentLoaded', function() {
const _container = document.querySelector('.tips');
const _title = document.querySelector('.tips .title');
const _description = document.querySelector('.tips .description');

const selectedTip = dataTips[Math.floor(Math.random() * dataTips.length)];
const ctaUrl = createURL(selectedTip.cta.url, selectedTip.cta.params)
const selectedTip = getRandomTip();
if (!selectedTip) {
// Remove the tips container if there are no tips to show
_container.remove();
return;
}

const linkUrl = createURL(selectedTip.link.url, selectedTip.link.params)

_title.innerHTML = selectedTip.title;
_description.innerHTML = `${selectedTip.description} <a href="${ctaUrl}" target="_blank">${selectedTip.cta.text}</a>`;
_description.innerHTML = `${selectedTip.description} <a href="${linkUrl}" target="_blank">${selectedTip.link.label}</a>`;
});
</script>

Expand Down
50 changes: 50 additions & 0 deletions web/apps/labelstudio/src/components/HeidiTips/liveContent.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,5 +238,55 @@
}
}
}
],
"authPage": [
{
"title": "Did you know?",
"description": "You can sync data from all popular cloud storage providers to collect new items for labeling as they are uploaded, and return the annotation results to train and continuously improve models.",
"link": {
"label": "Learn more",
"url": "https://labelstud.io/guide/storage",
"params": {
"experiment": "login_revamp",
"treatment": "sync_cloud_data"
}
}
},
{
"title": "Did you know?",
"description": "You can enable webhooks to trigger machine learning model training or perform active learning after a certain number of tasks have been annotated.",
"link": {
"label": "See use cases",
"url": "https://labelstud.io/guide/webhooks#What-to-use-Label-Studio-webhooks-for",
"params": {
"experiment": "login_revamp",
"treatment": "enable_webhooks"
}
}
},
{
"title": "Did you know?",
"description": "There's an Enterprise version of Label Studio packed with more features and automation to label data faster while ensuring the highest quality.",
"link": {
"label": "Learn more",
"url": "https://humansignal.com/goenterprise/",
"params": {
"experiment": "login_revamp",
"treatment": "enterprise_platform"
}
}
},
{
"title": "Did you know?",
"description": "Label Studio has dozens of pre-built templates for all data types you can use to configure your labeling UI, from image classification to sentiment analysis to supervised LLM fine-tuning. ",
"link": {
"label": "See all templates",
"url": "https://labelstud.io/templates",
"params": {
"experiment": "login_revamp",
"treatment": "templates"
}
}
}
]
}
2 changes: 1 addition & 1 deletion web/apps/labelstudio/src/components/HeidiTips/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const loadLiveTipsCollection = () => {
export function getRandomTip(collection: keyof TipsCollection): Tip | null {
const tipsCollection = loadLiveTipsCollection();

if (isTipDismissed(collection)) return null;
if (!tipsCollection[collection] || isTipDismissed(collection)) return null;

const tips = tipsCollection[collection];

Expand Down

0 comments on commit f86f208

Please sign in to comment.