Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions dags/sdk_doctor/github_sdk_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import time
from collections.abc import Callable
from datetime import UTC, datetime
from typing import Any, Literal, Optional, cast

import dagster
Expand Down Expand Up @@ -76,7 +77,8 @@ def fetch_github_data_for_sdk(lib_name: str) -> Optional[dict[str, Any]]:
def fetch_sdk_data_from_releases(repo: str, tag_prefix: str = "") -> Optional[dict[str, Any]]:
"""Helper function to fetch SDK data from GitHub releases API."""
try:
response = requests.get(f"https://api.github.com/repos/{repo}/releases", timeout=10)
# Fetch 100 releases (GitHub API max) to handle monorepos with many packages
response = requests.get(f"https://api.github.com/repos/{repo}/releases?per_page=100", timeout=10)
if not response.ok:
logger.error(f"[SDK Doctor] Failed to fetch releases for {repo}", status_code=response.status_code)
return None
Expand Down Expand Up @@ -376,14 +378,19 @@ def cache_github_sdk_versions_op(
cached_count = 0
skipped_count = 0

# Cache creation timestamp for staleness checks
cached_at = datetime.now(UTC).isoformat()

for lib_name, github_data in sdk_data.items():
if github_data is None:
skipped_count += 1
continue

cache_key = f"github:sdk_versions:{lib_name}"
try:
redis_client.setex(cache_key, CACHE_EXPIRY, json.dumps(github_data))
# Add cachedAt timestamp to the data
github_data_with_timestamp = {**github_data, "cachedAt": cached_at}
redis_client.setex(cache_key, CACHE_EXPIRY, json.dumps(github_data_with_timestamp))
cached_count += 1
context.log.info(f"Successfully cached {lib_name} SDK data")
except Exception as e:
Expand Down
10 changes: 8 additions & 2 deletions dags/sdk_doctor/team_sdk_versions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from collections import defaultdict
from dataclasses import dataclass
from datetime import UTC, datetime
from typing import Any, Literal, Optional

import dagster
Expand Down Expand Up @@ -100,9 +101,14 @@ def get_and_cache_team_sdk_versions(
try:
sdk_versions = get_sdk_versions_for_team(team_id, logger=logger)
if sdk_versions is not None:
payload = json.dumps(sdk_versions)
# Store actual timestamp when caching (instead of calculating from TTL later)
cached_at = datetime.now(UTC).isoformat()
cache_payload = {
"sdk_versions": sdk_versions,
"cachedAt": cached_at,
}
cache_key = f"sdk_versions:team:{team_id}"
redis_client.setex(cache_key, CACHE_EXPIRY, payload)
redis_client.setex(cache_key, CACHE_EXPIRY, json.dumps(cache_payload))
logger.info(f"[SDK Doctor] Team {team_id} SDK versions cached successfully")

return sdk_versions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LemonBanner, LemonButton, LemonTable, LemonTableColumns, LemonTag, Link

import { TZLabel } from 'lib/components/TZLabel'
import { FEATURE_FLAGS } from 'lib/constants'
import { dayjs } from 'lib/dayjs'
import { useOnMountEffect } from 'lib/hooks/useOnMountEffect'
import { IconWithBadge } from 'lib/lemon-ui/icons'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
Expand Down Expand Up @@ -110,16 +111,55 @@ const COLUMNS: LemonTableColumns<AugmentedTeamSdkVersionsInfoRelease> = [
{record.isOutdated ? (
<Tooltip
placement="right"
title={`Upgrade recommended ${record.daysSinceRelease ? `(${Math.floor(record.daysSinceRelease / 7)} weeks old)` : ''}`}
title={
record.releaseDate
? `Released ${dayjs(record.releaseDate).fromNow()}. Upgrade recommended.`
: 'Upgrade recommended'
}
>
<LemonTag type="danger" className="shrink-0">
Outdated
</LemonTag>
</Tooltip>
) : record.latestVersion && record.version === record.latestVersion ? (
<Tooltip
placement="right"
title={
<>
You have the latest available
{record.cachedAt ? ` as of ${dayjs(record.cachedAt).fromNow()}` : ''}.
<br />
Click 'Releases ↗' above to check for any since.
</>
}
>
<LemonTag type="success" className="shrink-0">
Current
</LemonTag>
</Tooltip>
) : (
<LemonTag type="success" className="shrink-0">
{record.latestVersion && record.version === record.latestVersion ? 'Current' : 'Recent'}
</LemonTag>
<Tooltip
placement="right"
title={
record.releaseDate ? (
<>
Released {dayjs(record.releaseDate).fromNow()}.
<br />
Upgrading is a good idea, but it's not urgent yet.
</>
) : (
"Upgrading is a good idea, but it's not urgent yet"
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Inconsistent quotation mark usage. This line uses double quotes while the surrounding JSX uses single quotes. Consider using single quotes for consistency with the rest of the codebase.

Suggested change
"Upgrading is a good idea, but it's not urgent yet"
'Upgrading is a good idea, but it\'s not urgent yet'

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4th commit

)
}
>
<LemonTag
type="warning"
className="shrink-0"
style={{ color: 'var(--warning-dark)', borderColor: 'var(--warning-dark)' }}
>
Recent
</LemonTag>
</Tooltip>
)}
</div>
)
Expand All @@ -128,7 +168,7 @@ const COLUMNS: LemonTableColumns<AugmentedTeamSdkVersionsInfoRelease> = [
{
title: (
<span>
LAST EVENT AT{' '}
LAST EVENT{' '}
<Tooltip title="This gets refreshed every night, click 'Scan Events' to refresh manually">
<IconInfo />
</Tooltip>
Expand All @@ -140,7 +180,7 @@ const COLUMNS: LemonTableColumns<AugmentedTeamSdkVersionsInfoRelease> = [
},
},
{
title: '# Events in the last 7 days',
title: '# events, last 7 days',
dataIndex: 'count',
render: function RenderCount(_, record) {
return <div className="text-xs text-muted-alt">{record.count}</div>
Expand Down Expand Up @@ -305,39 +345,34 @@ export const SidePanelSdkDoctorIcon = (props: { className?: string }): JSX.Eleme
}

function SdkSection({ sdkType }: { sdkType: SdkType }): JSX.Element {
const { sdkVersionsMap, teamSdkVersionsLoading } = useValues(sidePanelSdkDoctorLogic)
const { sdkVersionsMap, sdkVersions, teamSdkVersionsLoading } = useValues(sidePanelSdkDoctorLogic)

const sdk = sdkVersionsMap[sdkType]!
const cachedAt = sdkVersions?.[sdkType]?.cachedAt
const links = SDK_DOCS_LINKS[sdkType]
const sdkName = SDK_TYPE_READABLE_NAME[sdkType]

return (
<div className="flex flex-col mb-4 p-2">
<div className="flex flex-row justify-between items-center gap-2 mb-4">
<div>
<div className="flex flex-row items-center gap-2">
<h3 className="mb-0">{sdkName}</h3>
<span className="inline-flex gap-1">
<LemonTag type={sdk.isOutdated ? 'danger' : 'success'}>
{sdk.isOutdated ? 'Outdated' : 'Up to date'}
</LemonTag>

{sdk.isOld && (
<Tooltip
title={
sdk.allReleases[0]!.daysSinceRelease
? `This SDK is ${Math.floor(sdk.allReleases[0]!.daysSinceRelease / 7)} weeks old`
: 'This SDK is old and we suggest upgrading'
}
delayMs={0}
placement="right"
>
<LemonTag type="warning">Old</LemonTag>
</Tooltip>
)}
</span>
</div>
<small>Current version: {sdk.currentVersion}</small>
<h3 className="mb-0">{sdkName}</h3>
<small>
Latest version available:{' '}
<Tooltip
title={
<>
This is the latest available version
{cachedAt ? ` as of ${dayjs().diff(dayjs(cachedAt), 'hour')} hours ago` : ''}
.
<br />
Click 'Releases ↗', to the right, to check for any since.
</>
}
>
<span>{sdk.currentVersion}</span>
</Tooltip>
</small>
</div>

<div className="flex flex-row gap-2">
Expand Down
Loading
Loading