mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
Merge branch 'main' into teeohhem/expand-e2e-coverage
This commit is contained in:
commit
fd4094f3d2
67 changed files with 1133 additions and 261 deletions
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: Group Dashboards and Searches by Tag
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: Chart Explorer now auto-executes the chart on load when a valid source is configured. Deeplinks render results without requiring a manual click.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: Properly enable line wrap behavior in JSON viewer by default
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: Add dashboard template gallery
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: differentiate map indexing vs array indexing
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
perf: Defer expensive hooks in collapsed filter groups and virtualize nested filter lists
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: slider thumb and mark styling not applying theme tokens
|
||||
|
||||
- Move slider thumb styling from classNames to inline styles to fix CSS specificity issue where Mantine defaults override theme tokens
|
||||
- Add !important to slider mark styles to ensure token-based colors apply
|
||||
- Fix vertical centering of 6px slider mark dots within the 8px track
|
||||
- Remove broken translateX/translateY nudge that misaligned marks
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
"@hyperdx/common-utils": patch
|
||||
---
|
||||
|
||||
fix: show Map sub-fields in facet panel for non-LowCardinality value types
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/api": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: Add favoriting for dashboards and saved searches
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/api": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: Add more chart display units
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
fix: optimize order by should factor in wider cases, including the
|
||||
default otel_traces
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: Fixed bug preventing clicking into rows with nullable date types (and other misc type) columns.
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
"@hyperdx/common-utils": minor
|
||||
"@hyperdx/api": minor
|
||||
"@hyperdx/app": minor
|
||||
---
|
||||
|
||||
feat: new team setting for number of filters to fetch
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: render clickhouse keywords properly in codemirror
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: move help menu from footer to main nav links
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: Add $\_\_sourceTable macro
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: Fix query error when searching nested JSON values
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
"@hyperdx/common-utils": patch
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: Add saved searches listing page
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: Add source schema preview to SQL Charts and Trace Panel
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: replace sidebar collapse icons to align with ClickHouse collapse patterns
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
fix: Fix flaky E2E tests
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": minor
|
||||
---
|
||||
|
||||
feat: Add input filter pills below search input to make filters usage more clear on seach page.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: use 1 minute window for searches
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
'@hyperdx/common-utils': minor
|
||||
'@hyperdx/api': minor
|
||||
'@hyperdx/app': minor
|
||||
---
|
||||
|
||||
feat: support sample-weighted aggregations for sampled trace data
|
||||
4
.env
4
.env
|
|
@ -8,8 +8,8 @@ NEXT_ALL_IN_ONE_IMAGE_NAME_DOCKERHUB=clickhouse/clickstack-all-in-one
|
|||
ALL_IN_ONE_IMAGE_NAME_DOCKERHUB=hyperdx/hyperdx-all-in-one
|
||||
NEXT_OTEL_COLLECTOR_IMAGE_NAME_DOCKERHUB=clickhouse/clickstack-otel-collector
|
||||
OTEL_COLLECTOR_IMAGE_NAME_DOCKERHUB=hyperdx/hyperdx-otel-collector
|
||||
CODE_VERSION=2.22.1
|
||||
IMAGE_VERSION_SUB_TAG=.22.1
|
||||
CODE_VERSION=2.23.0
|
||||
IMAGE_VERSION_SUB_TAG=.23.0
|
||||
IMAGE_VERSION=2
|
||||
IMAGE_NIGHTLY_TAG=2-nightly
|
||||
IMAGE_LATEST_TAG=latest
|
||||
|
|
|
|||
13
.github/workflows/e2e-tests.yml
vendored
13
.github/workflows/e2e-tests.yml
vendored
|
|
@ -47,6 +47,19 @@ jobs:
|
|||
|
||||
- name: Start E2E Docker Compose
|
||||
run: |
|
||||
# Pre-pull images with retries to handle transient Docker Hub timeouts
|
||||
for attempt in 1 2 3; do
|
||||
if docker compose -p e2e-0 -f packages/app/tests/e2e/docker-compose.yml pull; then
|
||||
echo "Docker images pulled successfully on attempt $attempt"
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" -eq 3 ]; then
|
||||
echo "Failed to pull Docker images after 3 attempts"
|
||||
exit 1
|
||||
fi
|
||||
echo "Docker pull failed (attempt $attempt/3), retrying in 10s..."
|
||||
sleep 10
|
||||
done
|
||||
docker compose -p e2e-0 -f packages/app/tests/e2e/docker-compose.yml up -d
|
||||
echo "Waiting for MongoDB..."
|
||||
for i in $(seq 1 30); do
|
||||
|
|
|
|||
261
.github/workflows/pr-triage.yml
vendored
Normal file
261
.github/workflows/pr-triage.yml
vendored
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
name: PR Triage
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description:
|
||||
PR number to classify (leave blank to classify all open PRs)
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
classify:
|
||||
name: Classify PR risk tier
|
||||
runs-on: ubuntu-24.04
|
||||
# For pull_request events skip drafts; workflow_dispatch always runs
|
||||
if:
|
||||
${{ github.event_name == 'workflow_dispatch' ||
|
||||
!github.event.pull_request.draft }}
|
||||
steps:
|
||||
- name: Classify and label PR(s)
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
// ── Determine which PRs to process ──────────────────────────────
|
||||
let prNumbers;
|
||||
if (context.eventName === 'workflow_dispatch') {
|
||||
// Use context.payload.inputs to avoid script-injection via template interpolation
|
||||
const input = (context.payload.inputs?.pr_number ?? '').trim();
|
||||
if (input && input !== '') {
|
||||
prNumbers = [Number(input)];
|
||||
} else {
|
||||
const openPRs = await github.paginate(
|
||||
github.rest.pulls.list,
|
||||
{ owner, repo, state: 'open', per_page: 100 }
|
||||
);
|
||||
prNumbers = openPRs.map(pr => pr.number);
|
||||
console.log(`Bulk triage: found ${prNumbers.length} open PRs`);
|
||||
}
|
||||
} else {
|
||||
prNumbers = [context.payload.pull_request.number];
|
||||
}
|
||||
|
||||
// ── Shared constants ─────────────────────────────────────────────
|
||||
const TIER4_PATTERNS = [
|
||||
/^packages\/api\/src\/middleware\/auth/,
|
||||
/^packages\/api\/src\/routers\/api\/me\./,
|
||||
/^packages\/api\/src\/routers\/api\/team\./,
|
||||
/^packages\/api\/src\/routers\/external-api\//,
|
||||
/^packages\/api\/src\/models\/(user|team|teamInvite)\./,
|
||||
/^packages\/api\/src\/config\./,
|
||||
/^packages\/api\/src\/tasks\//,
|
||||
/^packages\/otel-collector\//,
|
||||
/^docker\/otel-collector\//,
|
||||
/^docker\/clickhouse\//,
|
||||
/^\.github\/workflows\//,
|
||||
];
|
||||
|
||||
const TIER1_PATTERNS = [
|
||||
/\.(md|txt|png|jpg|jpeg|gif|svg|ico)$/i,
|
||||
/^yarn\.lock$/,
|
||||
/^package-lock\.json$/,
|
||||
/^\.yarnrc\.yml$/,
|
||||
/^\.github\/images\//,
|
||||
/^\.env\.example$/,
|
||||
];
|
||||
|
||||
const BOT_AUTHORS = ['dependabot', 'dependabot[bot]'];
|
||||
const AGENT_BRANCH_PREFIXES = ['claude/', 'agent/', 'ai/'];
|
||||
|
||||
const TIER_LABELS = {
|
||||
1: { name: 'review/tier-1', color: '0E8A16', description: 'Trivial — auto-merge candidate once CI passes' },
|
||||
2: { name: 'review/tier-2', color: '1D76DB', description: 'Low risk — AI review + quick human skim' },
|
||||
3: { name: 'review/tier-3', color: 'E4E669', description: 'Standard — full human review required' },
|
||||
4: { name: 'review/tier-4', color: 'B60205', description: 'Critical — deep review + domain expert sign-off' },
|
||||
};
|
||||
|
||||
const TIER_INFO = {
|
||||
1: {
|
||||
emoji: '🟢',
|
||||
headline: 'Tier 1 — Trivial',
|
||||
detail: 'Docs, images, lock files, or a dependency bump. No functional code changes detected.',
|
||||
process: 'Auto-merge once CI passes. No human review required.',
|
||||
sla: 'Resolves automatically.',
|
||||
},
|
||||
2: {
|
||||
emoji: '🔵',
|
||||
headline: 'Tier 2 — Low Risk',
|
||||
detail: 'Small, isolated change with no API route or data model modifications.',
|
||||
process: 'AI review + quick human skim (target: 5–15 min). Reviewer validates AI assessment and checks for domain-specific concerns.',
|
||||
sla: 'Resolve within 4 business hours.',
|
||||
},
|
||||
3: {
|
||||
emoji: '🟡',
|
||||
headline: 'Tier 3 — Standard',
|
||||
detail: 'Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.',
|
||||
process: 'Full human review — logic, architecture, edge cases.',
|
||||
sla: 'First-pass feedback within 1 business day.',
|
||||
},
|
||||
4: {
|
||||
emoji: '🔴',
|
||||
headline: 'Tier 4 — Critical',
|
||||
detail: 'Touches auth, data models, config, tasks, OTel pipeline, ClickHouse, or CI/CD.',
|
||||
process: 'Deep review from a domain expert. Synchronous walkthrough may be required.',
|
||||
sla: 'Schedule synchronous review within 2 business days.',
|
||||
},
|
||||
};
|
||||
|
||||
// ── Ensure tier labels exist (once, before the loop) ─────────────
|
||||
const repoLabels = await github.paginate(
|
||||
github.rest.issues.listLabelsForRepo,
|
||||
{ owner, repo, per_page: 100 }
|
||||
);
|
||||
const repoLabelNames = new Set(repoLabels.map(l => l.name));
|
||||
for (const label of Object.values(TIER_LABELS)) {
|
||||
if (!repoLabelNames.has(label.name)) {
|
||||
await github.rest.issues.createLabel({ owner, repo, ...label });
|
||||
repoLabelNames.add(label.name);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Classify a single PR ─────────────────────────────────────────
|
||||
async function classifyPR(prNumber) {
|
||||
// Fetch changed files
|
||||
const filesRes = await github.paginate(
|
||||
github.rest.pulls.listFiles,
|
||||
{ owner, repo, pull_number: prNumber, per_page: 100 }
|
||||
);
|
||||
const files = filesRes.map(f => f.filename);
|
||||
const linesChanged = filesRes.reduce((sum, f) => sum + f.additions + f.deletions, 0);
|
||||
|
||||
// Fetch PR metadata
|
||||
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
||||
const author = pr.user.login;
|
||||
const branchName = pr.head.ref;
|
||||
|
||||
// Skip drafts when running in bulk mode
|
||||
if (pr.draft) {
|
||||
console.log(`Skipping PR #${prNumber}: draft`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for manual tier override — if a human last applied the label, respect it
|
||||
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({ owner, repo, issue_number: prNumber });
|
||||
const existingTierLabel = currentLabels.find(l => l.name.startsWith('review/tier-'));
|
||||
if (existingTierLabel) {
|
||||
const events = await github.paginate(
|
||||
github.rest.issues.listEvents,
|
||||
{ owner, repo, issue_number: prNumber, per_page: 100 }
|
||||
);
|
||||
const lastLabelEvent = events
|
||||
.filter(e => e.event === 'labeled' && e.label?.name === existingTierLabel.name)
|
||||
.pop();
|
||||
if (lastLabelEvent && lastLabelEvent.actor.type !== 'Bot') {
|
||||
console.log(`PR #${prNumber}: tier manually set to ${existingTierLabel.name} by ${lastLabelEvent.actor.login} — skipping`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Classify
|
||||
const isTier4 = files.some(f => TIER4_PATTERNS.some(p => p.test(f)));
|
||||
const isTrivialAuthor = BOT_AUTHORS.includes(author);
|
||||
const allFilesTrivial = files.length > 0 && files.every(f => TIER1_PATTERNS.some(p => p.test(f)));
|
||||
const isTier1 = isTrivialAuthor || allFilesTrivial;
|
||||
const isAgentBranch = AGENT_BRANCH_PREFIXES.some(p => branchName.startsWith(p));
|
||||
const touchesApiModels = files.some(f =>
|
||||
f.startsWith('packages/api/src/models/') || f.startsWith('packages/api/src/routers/')
|
||||
);
|
||||
const isSmallDiff = linesChanged < 100;
|
||||
// Agent branches are bumped to Tier 3 regardless of size to ensure human review
|
||||
const isTier2 = !isTier4 && !isTier1 && isSmallDiff && !touchesApiModels && !isAgentBranch;
|
||||
|
||||
let tier;
|
||||
if (isTier4) tier = 4;
|
||||
else if (isTier1) tier = 1;
|
||||
else if (isTier2) tier = 2;
|
||||
else tier = 3;
|
||||
|
||||
// Escalate very large non-critical PRs to Tier 4; this also applies to agent
|
||||
// branches that were bumped to Tier 3 above — a 400+ line agent-generated change
|
||||
// warrants deep review regardless of which files it touches.
|
||||
if (tier === 3 && linesChanged > 400) tier = 4;
|
||||
|
||||
// Apply label
|
||||
for (const existing of currentLabels) {
|
||||
if (existing.name.startsWith('review/tier-') && existing.name !== TIER_LABELS[tier].name) {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: existing.name });
|
||||
}
|
||||
}
|
||||
if (!currentLabels.find(l => l.name === TIER_LABELS[tier].name)) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: [TIER_LABELS[tier].name] });
|
||||
}
|
||||
|
||||
// Build comment body
|
||||
const info = TIER_INFO[tier];
|
||||
const signals = [];
|
||||
if (isTier4) signals.push('critical-path files detected');
|
||||
if (isAgentBranch) signals.push(`agent branch (\`${branchName}\`)`);
|
||||
if (linesChanged > 400) signals.push(`large diff (${linesChanged} lines changed)`);
|
||||
if (isTrivialAuthor) signals.push(`bot author (${author})`);
|
||||
if (allFilesTrivial && !isTrivialAuthor) signals.push('all files are docs/images/lock files');
|
||||
if (touchesApiModels) signals.push('API routes or data models changed');
|
||||
|
||||
const signalList = signals.length > 0 ? `\n**Signals**: ${signals.join(', ')}` : '';
|
||||
|
||||
const body = [
|
||||
'<!-- pr-triage -->',
|
||||
`## ${info.emoji} ${info.headline}`,
|
||||
'',
|
||||
info.detail,
|
||||
signalList,
|
||||
'',
|
||||
`**Review process**: ${info.process}`,
|
||||
`**SLA**: ${info.sla}`,
|
||||
'',
|
||||
`<details><summary>Stats</summary>`,
|
||||
'',
|
||||
`- Files changed: ${files.length}`,
|
||||
`- Lines changed: ${linesChanged}`,
|
||||
`- Branch: \`${branchName}\``,
|
||||
`- Author: ${author}`,
|
||||
'',
|
||||
'</details>',
|
||||
'',
|
||||
`> To override this classification, remove the \`${TIER_LABELS[tier].name}\` label and apply a different \`review/tier-*\` label. Manual overrides are preserved on subsequent pushes.`,
|
||||
].join('\n');
|
||||
|
||||
// Post or update the single triage comment
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments,
|
||||
{ owner, repo, issue_number: prNumber, per_page: 100 }
|
||||
);
|
||||
const existing = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.includes('<!-- pr-triage -->'));
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
|
||||
} else {
|
||||
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
|
||||
}
|
||||
|
||||
console.log(`PR #${prNumber}: Tier ${tier} (${linesChanged} lines, ${files.length} files)`);
|
||||
}
|
||||
|
||||
// ── Process all target PRs ───────────────────────────────────────
|
||||
for (const prNumber of prNumbers) {
|
||||
try {
|
||||
await classifyPR(prNumber);
|
||||
} catch (err) {
|
||||
console.error(`PR #${prNumber}: classification failed — ${err.message}`);
|
||||
}
|
||||
}
|
||||
21
AGENTS.md
21
AGENTS.md
|
|
@ -160,6 +160,27 @@ Before writing new E2E tests or reviewing a PR that adds them:
|
|||
- **Database patterns**: MongoDB for metadata with Mongoose, ClickHouse for
|
||||
telemetry queries
|
||||
|
||||
## PR Hygiene for Agent-Generated Code
|
||||
|
||||
When using agentic tools to generate PRs, follow these practices to keep reviews
|
||||
efficient and accurate:
|
||||
|
||||
1. **Scope PRs to a single logical change**, even if the agent can produce more
|
||||
in one session. Smaller, focused PRs move through the review pipeline faster
|
||||
and are easier to classify accurately.
|
||||
|
||||
2. **Write the PR description to explain intent (the "why"), not just what
|
||||
changed.** Reviewers need to understand the goal to catch cases where the
|
||||
agent solved the wrong problem or made a plausible-but-wrong trade-off.
|
||||
|
||||
3. **Name agent-generated branches with a `claude/`, `agent/`, or `ai/` prefix**
|
||||
(e.g., `claude/add-rate-limiting`). This allows the PR triage classifier to
|
||||
apply appropriate scrutiny and lets reviewers calibrate their attention.
|
||||
|
||||
4. **Write or update tests alongside the implementation**, not after. Configure
|
||||
your agent to produce tests before writing implementation code. See the
|
||||
Testing section below for the commands to use.
|
||||
|
||||
## GitHub Action Workflow (when invoked via @claude)
|
||||
|
||||
When working on issues or PRs through the GitHub Action:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,29 @@
|
|||
# @hyperdx/api
|
||||
|
||||
## 2.23.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- a15122b3: feat: new team setting for number of filters to fetch
|
||||
- 941d0450: feat: support sample-weighted aggregations for sampled trace data
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 53ba1e39: feat: Add favoriting for dashboards and saved searches
|
||||
- b7581db8: feat: Add more chart display units
|
||||
- 59b1f46f: fix: Show alerts on a tile only when dashboard matches
|
||||
- Updated dependencies [518bda7d]
|
||||
- Updated dependencies [4e54d850]
|
||||
- Updated dependencies [53ba1e39]
|
||||
- Updated dependencies [b7581db8]
|
||||
- Updated dependencies [48a8d32b]
|
||||
- Updated dependencies [a15122b3]
|
||||
- Updated dependencies [a55b151e]
|
||||
- Updated dependencies [308da30b]
|
||||
- Updated dependencies [e5c7fdf9]
|
||||
- Updated dependencies [941d0450]
|
||||
- @hyperdx/common-utils@0.17.0
|
||||
|
||||
## 2.22.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@hyperdx/api",
|
||||
"version": "2.22.1",
|
||||
"version": "2.23.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"engines": {
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
"@ai-sdk/anthropic": "^3.0.58",
|
||||
"@ai-sdk/openai": "^3.0.47",
|
||||
"@esm2cjs/p-queue": "^7.3.0",
|
||||
"@hyperdx/common-utils": "^0.16.2",
|
||||
"@hyperdx/common-utils": "^0.17.0",
|
||||
"@hyperdx/node-opentelemetry": "^0.9.0",
|
||||
"@hyperdx/passport-local-mongoose": "^9.0.1",
|
||||
"@opentelemetry/api": "^1.8.0",
|
||||
|
|
|
|||
|
|
@ -203,12 +203,14 @@ export const getAlertById = async (
|
|||
});
|
||||
};
|
||||
|
||||
export const getTeamDashboardAlertsByTile = async (teamId: ObjectId) => {
|
||||
export const getTeamDashboardAlertsByDashboardAndTile = async (
|
||||
teamId: ObjectId,
|
||||
) => {
|
||||
const alerts = await Alert.find({
|
||||
source: AlertSource.TILE,
|
||||
team: teamId,
|
||||
}).populate('createdBy', 'email name');
|
||||
return groupBy(alerts, 'tileId');
|
||||
return groupBy(alerts, a => `${a.dashboard?.toString()}:${a.tileId}`);
|
||||
};
|
||||
|
||||
export const getDashboardAlertsByTile = async (
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
createOrUpdateDashboardAlerts,
|
||||
deleteDashboardAlerts,
|
||||
getDashboardAlertsByTile,
|
||||
getTeamDashboardAlertsByTile,
|
||||
getTeamDashboardAlertsByDashboardAndTile,
|
||||
} from '@/controllers/alerts';
|
||||
import type { ObjectId } from '@/models';
|
||||
import type { AlertDocument, IAlert } from '@/models/alert';
|
||||
|
|
@ -96,7 +96,7 @@ async function syncDashboardAlerts(
|
|||
export async function getDashboards(teamId: ObjectId) {
|
||||
const [_dashboards, alerts] = await Promise.all([
|
||||
Dashboard.find({ team: teamId }),
|
||||
getTeamDashboardAlertsByTile(teamId),
|
||||
getTeamDashboardAlertsByDashboardAndTile(teamId),
|
||||
]);
|
||||
|
||||
const dashboards = _dashboards
|
||||
|
|
@ -105,7 +105,10 @@ export async function getDashboards(teamId: ObjectId) {
|
|||
...d,
|
||||
tiles: d.tiles.map(t => ({
|
||||
...t,
|
||||
config: { ...t.config, alert: alerts[t.id]?.[0] },
|
||||
config: {
|
||||
...t.config,
|
||||
alert: alerts[`${d._id.toString()}:${t.id}`]?.[0],
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -341,6 +341,48 @@ describe('dashboard router', () => {
|
|||
expect(allTilesPostDelete).toEqual(alertsPostDeleteTiles);
|
||||
});
|
||||
|
||||
it('alert on a tile only appears on the dashboard that owns it, not on another dashboard with the same tile ID', async () => {
|
||||
const sharedTileId = new mongoose.Types.ObjectId().toHexString();
|
||||
const mockAlert = makeMockAlert(webhook._id.toString());
|
||||
|
||||
// Create dashboard A with an alert on the tile
|
||||
const dashboardA = await agent
|
||||
.post('/dashboards')
|
||||
.send({
|
||||
name: 'Dashboard A',
|
||||
tiles: [makeTile({ id: sharedTileId, alert: mockAlert })],
|
||||
tags: [],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
// Create dashboard B with a tile that has the same ID, but no alert
|
||||
const dashboardB = await agent
|
||||
.post('/dashboards')
|
||||
.send({
|
||||
name: 'Dashboard B',
|
||||
tiles: [makeTile({ id: sharedTileId })],
|
||||
tags: [],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
// Fetch all dashboards
|
||||
const dashboards = await agent.get('/dashboards').expect(200);
|
||||
|
||||
const fetchedA = dashboards.body.find(
|
||||
(d: any) => d._id === dashboardA.body.id,
|
||||
);
|
||||
const fetchedB = dashboards.body.find(
|
||||
(d: any) => d._id === dashboardB.body.id,
|
||||
);
|
||||
|
||||
// The alert should appear on dashboard A's tile
|
||||
expect(fetchedA.tiles[0].config.alert).toBeTruthy();
|
||||
expect(fetchedA.tiles[0].config.alert.tileId).toBe(sharedTileId);
|
||||
|
||||
// The alert should NOT appear on dashboard B's tile
|
||||
expect(fetchedB.tiles[0].config.alert).toBeUndefined();
|
||||
});
|
||||
|
||||
it('preserves alert creator when different user updates dashboard', async () => {
|
||||
const mockAlert = makeMockAlert(webhook._id.toString());
|
||||
const currentUser = user;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,58 @@
|
|||
# @hyperdx/app
|
||||
|
||||
## 2.23.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- a15122b3: feat: new team setting for number of filters to fetch
|
||||
- 20e47207: feat: Add input filter pills below search input to make filters usage more clear on seach page.
|
||||
- 941d0450: feat: support sample-weighted aggregations for sampled trace data
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- bfc93811: feat: Group Dashboards and Searches by Tag
|
||||
- 859ced5c: feat: Chart Explorer now auto-executes the chart on load when a valid source is configured. Deeplinks render results without requiring a manual click.
|
||||
- e6a0455a: fix: Properly enable line wrap behavior in JSON viewer by default
|
||||
- 518bda7d: feat: Add dashboard template gallery
|
||||
- 676e4f4b: fix: differentiate map indexing vs array indexing
|
||||
- 9852e9b0: perf: Defer expensive hooks in collapsed filter groups and virtualize nested filter lists
|
||||
- 5e5c6a94: fix: slider thumb and mark styling not applying theme tokens
|
||||
|
||||
- Move slider thumb styling from classNames to inline styles to fix CSS specificity issue where Mantine defaults override theme tokens
|
||||
- Add !important to slider mark styles to ensure token-based colors apply
|
||||
- Fix vertical centering of 6px slider mark dots within the 8px track
|
||||
- Remove broken translateX/translateY nudge that misaligned marks
|
||||
|
||||
- 4e54d850: fix: show Map sub-fields in facet panel for non-LowCardinality value types
|
||||
- 011a245f: fix: Fix error state and table overflows
|
||||
- 53ba1e39: feat: Add favoriting for dashboards and saved searches
|
||||
- b7581db8: feat: Add more chart display units
|
||||
- 05a1b765: fix: optimize order by should factor in wider cases, including the
|
||||
default otel_traces
|
||||
- 48a8d32b: fix: Fixed bug preventing clicking into rows with nullable date types (and other misc type) columns.
|
||||
- a55b151e: fix: render clickhouse keywords properly in codemirror
|
||||
- 9cfb7e9c: fix: move help menu from footer to main nav links
|
||||
- 308da30b: feat: Add $\_\_sourceTable macro
|
||||
- 2bb8ccdc: fix: Fix query error when searching nested JSON values
|
||||
- df170d1e: fix: Show error on DBInfraPanel when correlated metric source is missing
|
||||
- e5c7fdf9: feat: Add saved searches listing page
|
||||
- 0cc1295d: fix: Add source schema preview to SQL Charts and Trace Panel
|
||||
- 1b77eab9: fix: replace sidebar collapse icons to align with ClickHouse collapse patterns
|
||||
- 853da16a: fix: Fix flaky E2E tests
|
||||
- b4e1498e: fix: Fix minor bugs in chart editor
|
||||
- bb24994f: feat: use 1 minute window for searches
|
||||
- Updated dependencies [518bda7d]
|
||||
- Updated dependencies [4e54d850]
|
||||
- Updated dependencies [53ba1e39]
|
||||
- Updated dependencies [b7581db8]
|
||||
- Updated dependencies [48a8d32b]
|
||||
- Updated dependencies [a15122b3]
|
||||
- Updated dependencies [a55b151e]
|
||||
- Updated dependencies [308da30b]
|
||||
- Updated dependencies [e5c7fdf9]
|
||||
- Updated dependencies [941d0450]
|
||||
- @hyperdx/common-utils@0.17.0
|
||||
|
||||
## 2.22.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import js from '@eslint/js';
|
|||
import tseslint from 'typescript-eslint';
|
||||
import storybook from 'eslint-plugin-storybook';
|
||||
import nextPlugin from '@next/eslint-plugin-next';
|
||||
import reactPlugin from 'eslint-plugin-react';
|
||||
import eslintReactPlugin from '@eslint-react/eslint-plugin';
|
||||
import reactHooksPlugin from 'eslint-plugin-react-hooks';
|
||||
import prettierConfig from 'eslint-config-prettier';
|
||||
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
||||
|
|
@ -112,16 +112,19 @@ export default [
|
|||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
plugins: {
|
||||
'@next/next': nextPlugin,
|
||||
react: reactPlugin,
|
||||
'react-hooks': reactHooksPlugin,
|
||||
'simple-import-sort': simpleImportSort,
|
||||
'react-hook-form': fixupPluginRules(reactHookFormPlugin), // not compatible with eslint 9 yet
|
||||
...eslintReactPlugin.configs.recommended.plugins,
|
||||
},
|
||||
rules: {
|
||||
...nextPlugin.configs.recommended.rules,
|
||||
...nextPlugin.configs['core-web-vitals'].rules,
|
||||
...reactHooksPlugin.configs.recommended.rules,
|
||||
'react-hooks/set-state-in-effect': 'warn',
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'react-hook-form/no-use-watch': 'error',
|
||||
'@eslint-react/no-unstable-default-props': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
|
|
@ -135,7 +138,6 @@ export default [
|
|||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'react/display-name': 'off',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
|
|
@ -155,9 +157,7 @@ export default [
|
|||
...UI_SYNTAX_RESTRICTIONS,
|
||||
...DATE_SYNTAX_RESTRICTIONS,
|
||||
],
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
'react-hook-form/no-use-watch': 'error',
|
||||
},
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@hyperdx/app",
|
||||
"version": "2.22.1",
|
||||
"version": "2.23.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
"@dagrejs/dagre": "^1.1.5",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@hyperdx/browser": "^0.22.0",
|
||||
"@hyperdx/common-utils": "^0.16.2",
|
||||
"@hyperdx/common-utils": "^0.17.0",
|
||||
"@hyperdx/node-opentelemetry": "^0.9.0",
|
||||
"@mantine/core": "^7.17.8",
|
||||
"@mantine/dates": "^7.17.8",
|
||||
|
|
@ -100,6 +100,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^4.1.3",
|
||||
"@eslint-react/eslint-plugin": "^3.0.0",
|
||||
"@eslint/compat": "^2.0.0",
|
||||
"@jedmao/location": "^3.0.0",
|
||||
"@next/eslint-plugin-next": "^16.0.10",
|
||||
|
|
@ -129,7 +130,6 @@
|
|||
"@types/sqlstring": "^2.3.2",
|
||||
"eslint-config-next": "^16.0.10",
|
||||
"eslint-plugin-playwright": "^2.4.0",
|
||||
"eslint-plugin-react": "^7.37.0",
|
||||
"eslint-plugin-react-hook-form": "^0.3.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-storybook": "10.1.4",
|
||||
|
|
|
|||
|
|
@ -784,7 +784,12 @@ function ClickhousePage() {
|
|||
</ChartBox>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<ChartBox style={{ height: 400 }}>
|
||||
<ChartBox
|
||||
style={{
|
||||
height: 400,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Text size="sm" mb="md">
|
||||
Slowest Queries
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -367,27 +367,28 @@ const Tile = forwardRef(
|
|||
>
|
||||
{(chart.config.displayType === DisplayType.Line ||
|
||||
chart.config.displayType === DisplayType.StackedBar ||
|
||||
chart.config.displayType === DisplayType.Number) && (
|
||||
<Indicator
|
||||
size={alert?.state === AlertState.OK ? 6 : 8}
|
||||
zIndex={1}
|
||||
color={alertIndicatorColor}
|
||||
processing={alert?.state === AlertState.ALERT}
|
||||
label={!alert && <span className="fs-8">+</span>}
|
||||
mr={4}
|
||||
>
|
||||
<Tooltip label={alertTooltip} withArrow>
|
||||
<ActionIcon
|
||||
data-testid={`tile-alerts-button-${chart.id}`}
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={onEditClick}
|
||||
>
|
||||
<IconBell size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Indicator>
|
||||
)}
|
||||
chart.config.displayType === DisplayType.Number) &&
|
||||
!isRawSqlSavedChartConfig(chart.config) && (
|
||||
<Indicator
|
||||
size={alert?.state === AlertState.OK ? 6 : 8}
|
||||
zIndex={1}
|
||||
color={alertIndicatorColor}
|
||||
processing={alert?.state === AlertState.ALERT}
|
||||
label={!alert && <span className="fs-8">+</span>}
|
||||
mr={4}
|
||||
>
|
||||
<Tooltip label={alertTooltip} withArrow>
|
||||
<ActionIcon
|
||||
data-testid={`tile-alerts-button-${chart.id}`}
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
onClick={onEditClick}
|
||||
>
|
||||
<IconBell size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Indicator>
|
||||
)}
|
||||
|
||||
<ActionIcon
|
||||
data-testid={`tile-duplicate-button-${chart.id}`}
|
||||
|
|
@ -469,7 +470,7 @@ const Tile = forwardRef(
|
|||
alertIndicatorColor,
|
||||
alertTooltip,
|
||||
availableSections,
|
||||
chart.config.displayType,
|
||||
chart.config,
|
||||
chart.id,
|
||||
chart.containerId,
|
||||
hovered,
|
||||
|
|
|
|||
|
|
@ -2077,7 +2077,11 @@ function DBSearchPage() {
|
|||
whiteSpace: 'pre-wrap',
|
||||
}}
|
||||
>
|
||||
<SQLPreview data={queryError.query} formatData />
|
||||
<SQLPreview
|
||||
data={queryError.query}
|
||||
formatData
|
||||
enableLineWrapping
|
||||
/>
|
||||
</Code>
|
||||
</Box>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -217,19 +217,14 @@ const LegendRenderer = memo<{
|
|||
selectedSeries?: Set<string>;
|
||||
onToggleSeries?: (seriesName: string, isShiftKey?: boolean) => void;
|
||||
}>(props => {
|
||||
const {
|
||||
payload = [],
|
||||
lineDataMap,
|
||||
allLineData = [],
|
||||
selectedSeries = new Set(),
|
||||
onToggleSeries,
|
||||
} = props;
|
||||
const { payload, lineDataMap, allLineData, selectedSeries, onToggleSeries } =
|
||||
props;
|
||||
|
||||
const hasSelection = selectedSeries.size > 0;
|
||||
const hasSelection = !!selectedSeries && selectedSeries.size > 0;
|
||||
|
||||
// Use allLineData to ensure all series are always shown in legend
|
||||
const allSeriesPayload = useMemo(() => {
|
||||
if (allLineData.length > 0) {
|
||||
if (allLineData?.length) {
|
||||
return allLineData.map(ld => ({
|
||||
dataKey: ld.dataKey,
|
||||
value: ld.displayName || ld.dataKey,
|
||||
|
|
@ -237,7 +232,7 @@ const LegendRenderer = memo<{
|
|||
payload: { strokeDasharray: ld.isDashed ? '4 3' : '0' },
|
||||
}));
|
||||
}
|
||||
return payload;
|
||||
return payload ?? [];
|
||||
}, [allLineData, payload]);
|
||||
|
||||
const sortedLegendItems = useMemo(() => {
|
||||
|
|
@ -268,7 +263,7 @@ const LegendRenderer = memo<{
|
|||
return (
|
||||
<div className={styles.legend}>
|
||||
{shownItems.map((entry, index) => {
|
||||
const isSelected = selectedSeries.has(entry.value);
|
||||
const isSelected = !!selectedSeries?.has(entry.value);
|
||||
const isDisabled = hasSelection && !isSelected;
|
||||
return (
|
||||
<ExpandableLegendItem
|
||||
|
|
@ -290,7 +285,7 @@ const LegendRenderer = memo<{
|
|||
<Popover.Dropdown p="xs">
|
||||
<div className={styles.legendTooltipContent}>
|
||||
{restItems.map((entry, index) => {
|
||||
const isSelected = selectedSeries.has(entry.value);
|
||||
const isSelected = !!selectedSeries?.has(entry.value);
|
||||
const isDisabled = hasSelection && !isSelected;
|
||||
return (
|
||||
<ExpandableLegendItem
|
||||
|
|
|
|||
|
|
@ -233,8 +233,8 @@ function ServiceSelectControlled({
|
|||
export function EndpointLatencyChart({
|
||||
source,
|
||||
dateRange,
|
||||
appliedConfig = {},
|
||||
extraFilters = [],
|
||||
appliedConfig,
|
||||
extraFilters,
|
||||
}: {
|
||||
source: TTraceSource;
|
||||
dateRange: [Date, Date];
|
||||
|
|
@ -284,9 +284,9 @@ export function EndpointLatencyChart({
|
|||
config={{
|
||||
source: source.id,
|
||||
...pickSourceConfigFields(source),
|
||||
where: appliedConfig.where || '',
|
||||
where: appliedConfig?.where || '',
|
||||
whereLanguage:
|
||||
(appliedConfig.whereLanguage ?? getStoredLanguage()) || 'sql',
|
||||
(appliedConfig?.whereLanguage ?? getStoredLanguage()) || 'sql',
|
||||
select: [
|
||||
// Separate the aggregations from the conversion to ms so that AggregatingMergeTree MVs can be used
|
||||
{
|
||||
|
|
@ -323,8 +323,11 @@ export function EndpointLatencyChart({
|
|||
},
|
||||
],
|
||||
filters: [
|
||||
...extraFilters,
|
||||
...getScopedFilters({ appliedConfig, expressions }),
|
||||
...(extraFilters ?? []),
|
||||
...getScopedFilters({
|
||||
appliedConfig: appliedConfig ?? {},
|
||||
expressions,
|
||||
}),
|
||||
],
|
||||
numberFormat: MS_NUMBER_FORMAT,
|
||||
dateRange,
|
||||
|
|
@ -337,9 +340,9 @@ export function EndpointLatencyChart({
|
|||
config={{
|
||||
source: source.id,
|
||||
...pickSourceConfigFields(source),
|
||||
where: appliedConfig.where || '',
|
||||
where: appliedConfig?.where || '',
|
||||
whereLanguage:
|
||||
(appliedConfig.whereLanguage ?? getStoredLanguage()) || 'sql',
|
||||
(appliedConfig?.whereLanguage ?? getStoredLanguage()) || 'sql',
|
||||
select: [
|
||||
{
|
||||
alias: 'data_nanoseconds',
|
||||
|
|
@ -353,8 +356,11 @@ export function EndpointLatencyChart({
|
|||
},
|
||||
],
|
||||
filters: [
|
||||
...extraFilters,
|
||||
...getScopedFilters({ appliedConfig, expressions }),
|
||||
...(extraFilters ?? []),
|
||||
...getScopedFilters({
|
||||
appliedConfig: appliedConfig ?? {},
|
||||
expressions,
|
||||
}),
|
||||
],
|
||||
dateRange,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@ function AggFnSelect({
|
|||
value,
|
||||
defaultValue,
|
||||
onChange,
|
||||
hideCustom,
|
||||
}: {
|
||||
value: string;
|
||||
defaultValue: string;
|
||||
onChange: (value: OnChangeValue) => void;
|
||||
hideCustom?: boolean;
|
||||
}) {
|
||||
const _onChange = useCallback(
|
||||
(value: string | null) => {
|
||||
|
|
@ -42,7 +44,7 @@ function AggFnSelect({
|
|||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
onChange={_onChange}
|
||||
data={AGG_FNS}
|
||||
data={hideCustom ? AGG_FNS.filter(fn => fn.value !== 'none') : AGG_FNS}
|
||||
data-testid="agg-fn-select"
|
||||
/>
|
||||
);
|
||||
|
|
@ -52,11 +54,13 @@ export function AggFnSelectControlled({
|
|||
aggFnName,
|
||||
quantileLevelName,
|
||||
defaultValue,
|
||||
hideCustom,
|
||||
...props
|
||||
}: {
|
||||
defaultValue: string;
|
||||
aggFnName: string;
|
||||
quantileLevelName: string;
|
||||
hideCustom?: boolean;
|
||||
} & Omit<UseControllerProps<any>, 'name'>) {
|
||||
const {
|
||||
field: { onChange: onAggFnChange, value: aggFnValue },
|
||||
|
|
@ -96,6 +100,7 @@ export function AggFnSelectControlled({
|
|||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
onChange={onChange}
|
||||
hideCustom={hideCustom}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export default function ContextSubpanel({
|
|||
dbSqlRowTableConfig,
|
||||
rowData,
|
||||
rowId,
|
||||
breadcrumbPath = [],
|
||||
breadcrumbPath,
|
||||
onBreadcrumbClick,
|
||||
}: ContextSubpanelProps) {
|
||||
const QUERY_KEY_PREFIX = 'context';
|
||||
|
|
@ -102,7 +102,7 @@ export default function ContextSubpanel({
|
|||
const [debouncedWhere] = useDebouncedValue(formWhere, 1000);
|
||||
|
||||
// State management for nested panels
|
||||
const isNested = breadcrumbPath.length > 0;
|
||||
const isNested = !!breadcrumbPath?.length;
|
||||
|
||||
const {
|
||||
contextRowId,
|
||||
|
|
@ -327,7 +327,7 @@ export default function ContextSubpanel({
|
|||
onClose={handleContextSidePanelClose}
|
||||
isNestedPanel={true}
|
||||
breadcrumbPath={[
|
||||
...breadcrumbPath,
|
||||
...(breadcrumbPath ?? []),
|
||||
{
|
||||
label: `Surrounding Context`,
|
||||
rowData,
|
||||
|
|
|
|||
|
|
@ -220,11 +220,17 @@ function ChartSeriesEditorComponent({
|
|||
const metricType = useWatch({ control, name: `${namePrefix}metricType` });
|
||||
|
||||
// Initialize metricType to 'gauge' when switching to a metric source
|
||||
// and reset 'custom' aggFn to 'count' since custom is not supported for metrics
|
||||
useEffect(() => {
|
||||
if (tableSource?.kind === SourceKind.Metric && !metricType) {
|
||||
setValue(`${namePrefix}metricType`, MetricsDataType.Gauge);
|
||||
if (tableSource?.kind === SourceKind.Metric) {
|
||||
if (!metricType) {
|
||||
setValue(`${namePrefix}metricType`, MetricsDataType.Gauge);
|
||||
}
|
||||
if (aggFn === 'none') {
|
||||
setValue(`${namePrefix}aggFn`, 'count');
|
||||
}
|
||||
}
|
||||
}, [tableSource?.kind, metricType, namePrefix, setValue]);
|
||||
}, [tableSource?.kind, metricType, aggFn, namePrefix, setValue]);
|
||||
|
||||
const tableName =
|
||||
tableSource?.kind === SourceKind.Metric
|
||||
|
|
@ -363,6 +369,7 @@ function ChartSeriesEditorComponent({
|
|||
quantileLevelName={`${namePrefix}level`}
|
||||
defaultValue={AGG_FNS[0]?.value ?? 'avg'}
|
||||
control={control}
|
||||
hideCustom={tableSource?.kind === SourceKind.Metric}
|
||||
/>
|
||||
</div>
|
||||
{tableSource?.kind === SourceKind.Metric && metricType && (
|
||||
|
|
@ -496,7 +503,7 @@ function ChartSeriesEditorComponent({
|
|||
language={aggConditionLanguage === 'sql' ? 'sql' : 'lucene'}
|
||||
metricMetadata={metricMetadata}
|
||||
onAddToWhere={handleAddToWhere}
|
||||
onAddToGroupBy={handleAddToGroupBy}
|
||||
onAddToGroupBy={showGroupBy ? handleAddToGroupBy : undefined}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export type HighlightedAttribute = {
|
|||
};
|
||||
|
||||
export function DBHighlightedAttributesList({
|
||||
attributes = [],
|
||||
attributes,
|
||||
}: {
|
||||
attributes: HighlightedAttribute[];
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { add, min, sub } from 'date-fns';
|
||||
import {
|
||||
convertDateRangeToGranularityString,
|
||||
|
|
@ -12,16 +13,23 @@ import {
|
|||
TSource,
|
||||
} from '@hyperdx/common-utils/dist/types';
|
||||
import {
|
||||
Alert,
|
||||
Anchor,
|
||||
Box,
|
||||
Card,
|
||||
Group,
|
||||
Modal,
|
||||
ScrollArea,
|
||||
SegmentedControl,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
|
||||
import { convertV1ChartConfigToV2 } from '@/ChartUtils';
|
||||
import { TableSourceForm } from '@/components/Sources/SourceForm';
|
||||
import { IS_LOCAL_MODE } from '@/config';
|
||||
import { useSource } from '@/source';
|
||||
|
||||
import {
|
||||
|
|
@ -211,11 +219,14 @@ export default ({
|
|||
rowId: string | undefined | null;
|
||||
source: TSource;
|
||||
}) => {
|
||||
const [editModalOpened, { open: openEditModal, close: closeEditModal }] =
|
||||
useDisclosure(false);
|
||||
|
||||
const metricSourceId =
|
||||
isLogSource(source) || isTraceSource(source)
|
||||
? source.metricSourceId
|
||||
: undefined;
|
||||
const { data: metricSource } = useSource({
|
||||
const { data: metricSource, isLoading: isLoadingMetricSource } = useSource({
|
||||
id: metricSourceId,
|
||||
kinds: [SourceKind.Metric],
|
||||
});
|
||||
|
|
@ -227,6 +238,39 @@ export default ({
|
|||
|
||||
return (
|
||||
<Stack my="md" gap={40}>
|
||||
{!metricSource && !isLoadingMetricSource && (
|
||||
<>
|
||||
<Alert color="yellow" title="No correlated metric source">
|
||||
<Text size="sm">
|
||||
{metricSourceId
|
||||
? `The correlated metric source for "${source.name}" could not be found.`
|
||||
: `Source "${source.name}" does not have a correlated metric source.`}{' '}
|
||||
Infrastructure metrics can be displayed when a metric source is
|
||||
configured in{' '}
|
||||
{IS_LOCAL_MODE ? (
|
||||
<Anchor component="button" onClick={openEditModal}>
|
||||
Source Settings
|
||||
</Anchor>
|
||||
) : (
|
||||
<Anchor component={Link} href="/team">
|
||||
Team Settings
|
||||
</Anchor>
|
||||
)}
|
||||
.
|
||||
</Text>
|
||||
</Alert>
|
||||
{IS_LOCAL_MODE && (
|
||||
<Modal
|
||||
size="xl"
|
||||
opened={editModalOpened}
|
||||
onClose={closeEditModal}
|
||||
title="Edit Source"
|
||||
>
|
||||
<TableSourceForm sourceId={source.id} />
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{podUid && (
|
||||
<div>
|
||||
{metricSource && (
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ export default function DBListBarChart({
|
|||
enabled,
|
||||
valueColumn,
|
||||
groupColumn,
|
||||
hiddenSeries = [],
|
||||
hiddenSeries,
|
||||
title,
|
||||
toolbarItems,
|
||||
showMVOptimizationIndicator = true,
|
||||
|
|
@ -220,7 +220,7 @@ export default function DBListBarChart({
|
|||
}
|
||||
|
||||
return Object.keys(rows?.[0])
|
||||
.filter(key => !hiddenSeries.includes(key))
|
||||
.filter(key => !hiddenSeries?.includes(key))
|
||||
.map(key => ({
|
||||
dataKey: key,
|
||||
displayName: key,
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ function HyperJsonMenu({ rowData }: { rowData: any }) {
|
|||
|
||||
export function DBRowJsonViewer({
|
||||
data,
|
||||
jsonColumns = [],
|
||||
jsonColumns,
|
||||
}: {
|
||||
data: any;
|
||||
jsonColumns?: string[];
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ const DBRowSidePanel = ({
|
|||
isNestedPanel = false,
|
||||
setSubDrawerOpen,
|
||||
onClose,
|
||||
breadcrumbPath = [],
|
||||
breadcrumbPath,
|
||||
onBreadcrumbClick,
|
||||
}: DBRowSidePanelProps & {
|
||||
setSubDrawerOpen: Dispatch<SetStateAction<boolean>>;
|
||||
|
|
@ -127,7 +127,7 @@ const DBRowSidePanel = ({
|
|||
const handleBreadcrumbClick = useCallback(
|
||||
(targetLevel: number) => {
|
||||
// Current panel's level in the hierarchy
|
||||
const currentLevel = breadcrumbPath.length;
|
||||
const currentLevel = breadcrumbPath?.length ?? 0;
|
||||
|
||||
// The target panel level corresponds to the breadcrumb index:
|
||||
// - targetLevel 0 = root panel (breadcrumbPath.length = 0)
|
||||
|
|
@ -149,7 +149,7 @@ const DBRowSidePanel = ({
|
|||
onBreadcrumbClick?.(targetLevel);
|
||||
}
|
||||
},
|
||||
[breadcrumbPath.length, onBreadcrumbClick, onClose],
|
||||
[breadcrumbPath?.length, onBreadcrumbClick, onClose],
|
||||
);
|
||||
|
||||
const hasOverviewPanel = useMemo(() => {
|
||||
|
|
@ -551,7 +551,7 @@ export default function DBRowSidePanelErrorBoundary({
|
|||
aliasWith,
|
||||
source,
|
||||
isNestedPanel,
|
||||
breadcrumbPath = [],
|
||||
breadcrumbPath,
|
||||
onBreadcrumbClick,
|
||||
}: DBRowSidePanelProps) {
|
||||
const contextZIndex = useZIndex();
|
||||
|
|
|
|||
|
|
@ -127,13 +127,13 @@ function BreadcrumbNavigation({
|
|||
}
|
||||
|
||||
export default function DBRowSidePanelHeader({
|
||||
attributes = [],
|
||||
attributes,
|
||||
mainContent = '',
|
||||
mainContentHeader,
|
||||
date,
|
||||
severityText,
|
||||
rowData,
|
||||
breadcrumbPath = [],
|
||||
breadcrumbPath,
|
||||
onBreadcrumbClick,
|
||||
}: {
|
||||
date: Date;
|
||||
|
|
@ -197,11 +197,19 @@ export default function DBRowSidePanelHeader({
|
|||
[generateSearchUrl],
|
||||
);
|
||||
|
||||
const breadCrumbPathWithDefault = useMemo(() => {
|
||||
return breadcrumbPath ?? [];
|
||||
}, [breadcrumbPath]);
|
||||
|
||||
const attributesWithDefault = useMemo(() => {
|
||||
return attributes ?? [];
|
||||
}, [attributes]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Breadcrumb navigation */}
|
||||
<BreadcrumbNavigation
|
||||
breadcrumbPath={breadcrumbPath}
|
||||
breadcrumbPath={breadCrumbPathWithDefault}
|
||||
onNavigateToLevel={onBreadcrumbClick}
|
||||
/>
|
||||
|
||||
|
|
@ -277,7 +285,7 @@ export default function DBRowSidePanelHeader({
|
|||
)}
|
||||
<AISummarizeButton rowData={rowData} severityText={severityText} />
|
||||
<Box mt="xs">
|
||||
<DBHighlightedAttributesList attributes={attributes} />
|
||||
<DBHighlightedAttributesList attributes={attributesWithDefault} />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -330,15 +330,17 @@ const FilterRangeDisplay = ({
|
|||
);
|
||||
};
|
||||
|
||||
type SelectedValues = {
|
||||
included: Set<string | boolean>;
|
||||
excluded: Set<string | boolean>;
|
||||
range?: { min: number; max: number };
|
||||
};
|
||||
|
||||
export type FilterGroupProps = {
|
||||
name: string;
|
||||
options: { value: string | boolean; label: string }[];
|
||||
optionsLoading?: boolean;
|
||||
selectedValues?: {
|
||||
included: Set<string | boolean>;
|
||||
excluded: Set<string | boolean>;
|
||||
range?: { min: number; max: number };
|
||||
};
|
||||
selectedValues?: SelectedValues;
|
||||
onChange: (value: string | boolean) => void;
|
||||
onClearClick: VoidFunction;
|
||||
onOnlyClick: (value: string | boolean) => void;
|
||||
|
|
@ -835,7 +837,7 @@ export const FilterGroup = ({
|
|||
name,
|
||||
options,
|
||||
optionsLoading,
|
||||
selectedValues = { included: new Set(), excluded: new Set() },
|
||||
selectedValues: _selectedValues,
|
||||
onChange,
|
||||
onClearClick,
|
||||
onOnlyClick,
|
||||
|
|
@ -860,6 +862,11 @@ export const FilterGroup = ({
|
|||
const [showDistributions, setShowDistributions] = useState(false);
|
||||
const [isFetchingDistribution, setIsFetchingDistribution] = useState(false);
|
||||
|
||||
const selectedValues: SelectedValues = useMemo(
|
||||
() => _selectedValues ?? { included: new Set(), excluded: new Set() },
|
||||
[_selectedValues],
|
||||
);
|
||||
|
||||
const hasRange = selectedValues.range != null;
|
||||
|
||||
const toggleShowDistributions = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ const MAX_VIRTUAL_LIST_HEIGHT = 350;
|
|||
export const NestedFilterGroup = ({
|
||||
name,
|
||||
childFilters,
|
||||
selectedValues = {},
|
||||
selectedValues: _selectedValues,
|
||||
onChange,
|
||||
onClearClick,
|
||||
onOnlyClick,
|
||||
|
|
@ -60,6 +60,11 @@ export const NestedFilterGroup = ({
|
|||
chartConfig,
|
||||
isLive,
|
||||
}: NestedFilterGroupProps) => {
|
||||
const selectedValues: FilterState = useMemo(
|
||||
() => _selectedValues ?? {},
|
||||
[_selectedValues],
|
||||
);
|
||||
|
||||
const totalFiltersSize = useMemo(
|
||||
() =>
|
||||
Object.values(selectedValues).reduce(
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default function DBTableChart({
|
|||
queryKeyPrefix,
|
||||
onSortingChange,
|
||||
sort: controlledSort,
|
||||
hiddenColumns = [],
|
||||
hiddenColumns,
|
||||
title,
|
||||
toolbarPrefix,
|
||||
toolbarSuffix,
|
||||
|
|
@ -135,7 +135,7 @@ export default function DBTableChart({
|
|||
}
|
||||
|
||||
return Object.keys(rows?.[0])
|
||||
.filter(key => !hiddenColumns.includes(key))
|
||||
.filter(key => !hiddenColumns?.includes(key))
|
||||
.map(key => ({
|
||||
// If it's an alias, wrap in quotes to support a variety of formats (ex "Time (ms)", "Req/s", etc)
|
||||
id: aliasMap.includes(key) ? `"${key}"` : key,
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ const Line = React.memo(
|
|||
value,
|
||||
disableMenu,
|
||||
isInParsedJson = false,
|
||||
parsedJsonRootPath = [],
|
||||
parsedJsonRootPath,
|
||||
}: {
|
||||
keyName: string;
|
||||
keyPath: string[];
|
||||
|
|
@ -230,7 +230,7 @@ const Line = React.memo(
|
|||
// This is the start of a new parsed JSON context
|
||||
return keyPath;
|
||||
}
|
||||
return parsedJsonRootPath;
|
||||
return parsedJsonRootPath ?? [];
|
||||
}, [isStringValueValidJson, keyPath, parsedJsonRootPath]);
|
||||
|
||||
// Hide LineMenu when selecting text in the value
|
||||
|
|
@ -320,10 +320,10 @@ const Line = React.memo(
|
|||
const MAX_TREE_NODE_ITEMS = 50;
|
||||
function TreeNode({
|
||||
data,
|
||||
keyPath = [],
|
||||
keyPath: _keyPath,
|
||||
disableMenu = false,
|
||||
isInParsedJson = false,
|
||||
parsedJsonRootPath = [],
|
||||
parsedJsonRootPath,
|
||||
}: {
|
||||
data: object;
|
||||
keyPath?: string[];
|
||||
|
|
@ -333,6 +333,8 @@ function TreeNode({
|
|||
}) {
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
|
||||
const keyPath = React.useMemo(() => _keyPath ?? [], [_keyPath]);
|
||||
|
||||
const originalLength = React.useMemo(() => Object.keys(data).length, [data]);
|
||||
const visibleLines = React.useMemo(() => {
|
||||
return isExpanded
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ interface MetricAttributeHelperPanelProps {
|
|||
language: 'sql' | 'lucene';
|
||||
metricMetadata?: MetricMetadata | null;
|
||||
onAddToWhere: (clause: string) => void;
|
||||
onAddToGroupBy: (clause: string) => void;
|
||||
onAddToGroupBy?: (clause: string) => void;
|
||||
}
|
||||
|
||||
const CATEGORY_LABELS: Record<AttributeCategory, string> = {
|
||||
|
|
@ -170,7 +170,7 @@ interface AttributeValueListProps {
|
|||
language: 'sql' | 'lucene';
|
||||
onAddToWhere: (clause: string) => void;
|
||||
onBack: () => void;
|
||||
onAddToGroupBy: (clause: string) => void;
|
||||
onAddToGroupBy?: (clause: string) => void;
|
||||
}
|
||||
|
||||
function AttributeValueList({
|
||||
|
|
@ -217,7 +217,7 @@ function AttributeValueList({
|
|||
attribute.name,
|
||||
'sql',
|
||||
);
|
||||
onAddToGroupBy(clause);
|
||||
onAddToGroupBy?.(clause);
|
||||
}, [attribute, onAddToGroupBy]);
|
||||
|
||||
return (
|
||||
|
|
@ -234,14 +234,16 @@ function AttributeValueList({
|
|||
</Badge>
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="xs"
|
||||
leftSection={<IconPlus size={14} />}
|
||||
onClick={handleAddToGroupBy}
|
||||
>
|
||||
Group By
|
||||
</Button>
|
||||
{onAddToGroupBy && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="xs"
|
||||
leftSection={<IconPlus size={14} />}
|
||||
onClick={handleAddToGroupBy}
|
||||
>
|
||||
Group By
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<TextInput
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default function SlowestEventsTile({
|
|||
title,
|
||||
queryKeyPrefix,
|
||||
enabled = true,
|
||||
extraFilters = [],
|
||||
extraFilters,
|
||||
}: {
|
||||
source: TTraceSource;
|
||||
dateRange: [Date, Date];
|
||||
|
|
@ -54,7 +54,7 @@ export default function SlowestEventsTile({
|
|||
},
|
||||
],
|
||||
dateRange,
|
||||
filters: [...extraFilters],
|
||||
filters: extraFilters,
|
||||
},
|
||||
{
|
||||
placeholderData: (prev: any) => prev,
|
||||
|
|
@ -152,7 +152,7 @@ export default function SlowestEventsTile({
|
|||
limit: { limit: 200 },
|
||||
dateRange,
|
||||
filters: [
|
||||
...extraFilters,
|
||||
...(extraFilters ?? []),
|
||||
{
|
||||
type: 'sql',
|
||||
condition: `${expressions.durationInMillis} > ${roundedP95}`,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ type TableProps<T extends Record<string, unknown> | string[]> = {
|
|||
|
||||
// TODO: Retire this component in favor of Mantine
|
||||
export const Table = <T extends Record<string, unknown> | string[]>({
|
||||
data = [],
|
||||
data,
|
||||
columns,
|
||||
emptyMessage,
|
||||
hideHeader,
|
||||
|
|
@ -35,13 +35,13 @@ export const Table = <T extends Record<string, unknown> | string[]>({
|
|||
tableMeta,
|
||||
}: TableProps<T>) => {
|
||||
const table = useReactTable({
|
||||
data,
|
||||
data: data ?? [],
|
||||
columns,
|
||||
meta: tableMeta,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
if (!data.length) {
|
||||
if (!data?.length) {
|
||||
return <div className={styles.emptyMessage}>{emptyMessage}</div>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useMemo } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse';
|
||||
import { Button, Code, Group, Modal, Stack, Text } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
|
|
@ -45,7 +46,14 @@ export default function ChartErrorState({
|
|||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="h-100 w-100 d-flex g-1 flex-column align-items-center justify-content-center text-muted overflow-auto">
|
||||
<div
|
||||
className={cx(
|
||||
'h-100 w-100 d-flex g-1 flex-column align-items-center text-muted overflow-scroll',
|
||||
{
|
||||
'justify-content-center': variant === 'collapsible',
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Text ta="center" size="sm" my="sm">
|
||||
Error loading chart, please check your query or try again later.
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export class ChartEditorComponent {
|
|||
private readonly chartTypeInput: Locator;
|
||||
private readonly sourceSelector: Locator;
|
||||
private readonly metricSelector: Locator;
|
||||
private readonly aggFnSelect: Locator;
|
||||
private readonly addOrRemoveAlertButton: Locator;
|
||||
private readonly webhookSelector: Locator;
|
||||
private readonly runQueryButton: Locator;
|
||||
|
|
@ -29,6 +30,7 @@ export class ChartEditorComponent {
|
|||
this.chartTypeInput = page.getByTestId('chart-type-input');
|
||||
this.sourceSelector = page.getByTestId('source-selector');
|
||||
this.metricSelector = page.getByTestId('metric-name-selector');
|
||||
this.aggFnSelect = page.getByTestId('agg-fn-select');
|
||||
this.addOrRemoveAlertButton = page.getByTestId('alert-button');
|
||||
this.webhookSelector = page.getByTestId('select-webhook');
|
||||
this.addNewWebhookButton = page.getByTestId('add-new-webhook-button');
|
||||
|
|
@ -99,6 +101,33 @@ export class ChartEditorComponent {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select an aggregation function from the dropdown
|
||||
*/
|
||||
async selectAggFn(label: string) {
|
||||
await this.aggFnSelect.click();
|
||||
await this.page.getByRole('option', { name: label }).click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected aggregation function value
|
||||
*/
|
||||
async getSelectedAggFn(): Promise<string | null> {
|
||||
return this.aggFnSelect.inputValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an aggregation function option is available in the dropdown
|
||||
*/
|
||||
async isAggFnOptionAvailable(label: string): Promise<boolean> {
|
||||
await this.aggFnSelect.click();
|
||||
const option = this.page.getByRole('option', { name: label });
|
||||
const visible = await option.isVisible().catch(() => false);
|
||||
// Close the dropdown
|
||||
await this.page.keyboard.press('Escape');
|
||||
return visible;
|
||||
}
|
||||
|
||||
async clickAddAlert() {
|
||||
await this.addOrRemoveAlertButton.click();
|
||||
this.addNewWebhookButton.waitFor({
|
||||
|
|
@ -250,6 +279,10 @@ export class ChartEditorComponent {
|
|||
return this.metricSelector;
|
||||
}
|
||||
|
||||
get aggFn() {
|
||||
return this.aggFnSelect;
|
||||
}
|
||||
|
||||
get alertButton() {
|
||||
return this.addOrRemoveAlertButton;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { SearchPage } from '../page-objects/SearchPage';
|
||||
import { expect, test } from '../utils/base-test';
|
||||
|
||||
test.describe('Correlated Metric Source', { tag: ['@full-stack'] }, () => {
|
||||
let searchPage: SearchPage;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
searchPage = new SearchPage(page);
|
||||
});
|
||||
|
||||
test('should show alert when no correlated metric source is configured', async ({
|
||||
page,
|
||||
}) => {
|
||||
// Navigate to search page
|
||||
await searchPage.goto();
|
||||
|
||||
// Select the source without metricSourceId
|
||||
await searchPage.selectSource('E2E K8s Logs No Metrics');
|
||||
|
||||
// Search for K8s events that have k8s.pod.uid resource attribute
|
||||
await searchPage.performSearch('ResourceAttributes.k8s.pod.uid:*');
|
||||
|
||||
// Click on first row to open side panel
|
||||
await searchPage.table.clickFirstRow();
|
||||
|
||||
// Click the Infrastructure tab
|
||||
await searchPage.sidePanel.clickTab('infrastructure');
|
||||
|
||||
// Assert the "No correlated metric source" alert is visible
|
||||
await expect(page.getByText('No correlated metric source')).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('does not have a correlated metric source'),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
|
@ -793,6 +793,41 @@ test.describe('Dashboard', { tag: ['@dashboard'] }, () => {
|
|||
});
|
||||
});
|
||||
|
||||
test(
|
||||
'should deselect and hide the Custom aggregation function when switching to a metric source',
|
||||
{ tag: '@full-stack' },
|
||||
async () => {
|
||||
await test.step('Navigate to dashboard and open new tile editor', async () => {
|
||||
await dashboardPage.openNewTileEditor();
|
||||
});
|
||||
|
||||
await test.step('Select the "Custom" aggregation function', async () => {
|
||||
await dashboardPage.chartEditor.selectAggFn('Custom');
|
||||
const selectedAggFn =
|
||||
await dashboardPage.chartEditor.getSelectedAggFn();
|
||||
expect(selectedAggFn).toBe('Custom');
|
||||
});
|
||||
|
||||
await test.step('Switch the source to a metric source', async () => {
|
||||
await dashboardPage.chartEditor.selectSource(
|
||||
DEFAULT_METRICS_SOURCE_NAME,
|
||||
);
|
||||
});
|
||||
|
||||
await test.step('Verify the aggregation function was automatically changed away from "Custom"', async () => {
|
||||
const selectedAggFn =
|
||||
await dashboardPage.chartEditor.getSelectedAggFn();
|
||||
expect(selectedAggFn).toBe('Count of Events');
|
||||
});
|
||||
|
||||
await test.step('Verify the "Custom" option is NOT available in the aggregation dropdown', async () => {
|
||||
const isCustomAvailable =
|
||||
await dashboardPage.chartEditor.isAggFnOptionAvailable('Custom');
|
||||
expect(isCustomAvailable).toBe(false);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
'should clear saved query when WHERE input is cleared and saved',
|
||||
{},
|
||||
|
|
|
|||
|
|
@ -92,6 +92,23 @@
|
|||
"traceIdExpression": "TraceId",
|
||||
"spanIdExpression": "SpanId",
|
||||
"implicitColumnExpression": "Body"
|
||||
},
|
||||
{
|
||||
"id": "E2E K8s Logs No Metrics",
|
||||
"kind": "log",
|
||||
"name": "E2E K8s Logs No Metrics",
|
||||
"connection": "local",
|
||||
"from": { "databaseName": "default", "tableName": "e2e_otel_logs" },
|
||||
"timestampValueExpression": "TimestampTime",
|
||||
"defaultTableSelectExpression": "Timestamp, ServiceName, SeverityText, Body",
|
||||
"serviceNameExpression": "ServiceName",
|
||||
"severityTextExpression": "SeverityText",
|
||||
"eventAttributesExpression": "LogAttributes",
|
||||
"resourceAttributesExpression": "ResourceAttributes",
|
||||
"traceIdExpression": "TraceId",
|
||||
"spanIdExpression": "SpanId",
|
||||
"implicitColumnExpression": "Body",
|
||||
"displayedTimestampValueExpression": "Timestamp"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,23 @@
|
|||
# @hyperdx/common-utils
|
||||
|
||||
## 0.17.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- a15122b3: feat: new team setting for number of filters to fetch
|
||||
- 941d0450: feat: support sample-weighted aggregations for sampled trace data
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 518bda7d: feat: Add dashboard template gallery
|
||||
- 4e54d850: fix: show Map sub-fields in facet panel for non-LowCardinality value types
|
||||
- 53ba1e39: feat: Add favoriting for dashboards and saved searches
|
||||
- b7581db8: feat: Add more chart display units
|
||||
- 48a8d32b: fix: Fixed bug preventing clicking into rows with nullable date types (and other misc type) columns.
|
||||
- a55b151e: fix: render clickhouse keywords properly in codemirror
|
||||
- 308da30b: feat: Add $\_\_sourceTable macro
|
||||
- e5c7fdf9: feat: Add saved searches listing page
|
||||
|
||||
## 0.16.2
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@hyperdx/common-utils",
|
||||
"description": "Common utilities for HyperDX application",
|
||||
"version": "0.16.2",
|
||||
"version": "0.17.0",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"files": [
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# @hyperdx/otel-collector
|
||||
|
||||
## 2.23.0
|
||||
|
||||
## 2.22.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@hyperdx/otel-collector",
|
||||
"description": "HyperDX OpenTelemetry Collector configuration and Docker image",
|
||||
"version": "2.22.1",
|
||||
"version": "2.23.0",
|
||||
"license": "MIT",
|
||||
"private": true
|
||||
}
|
||||
|
|
|
|||
363
yarn.lock
363
yarn.lock
|
|
@ -3878,6 +3878,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.9.1":
|
||||
version: 4.9.1
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.9.1"
|
||||
dependencies:
|
||||
eslint-visitor-keys: "npm:^3.4.3"
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||
checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1":
|
||||
version: 4.12.2
|
||||
resolution: "@eslint-community/regexpp@npm:4.12.2"
|
||||
|
|
@ -3892,6 +3903,92 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-react/ast@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@eslint-react/ast@npm:3.0.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:^8.57.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:^8.57.0"
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
string-ts: "npm:^2.3.1"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/d22565903f78061a5b2b6cc093209c50b941311d0fe760dc8eb74b9a7792968801f02eba3de7e3ff981e25fc36bdcbd3b5c1e340941b201a57bebf5683b5cdc9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-react/core@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@eslint-react/core@npm:3.0.0"
|
||||
dependencies:
|
||||
"@eslint-react/ast": "npm:3.0.0"
|
||||
"@eslint-react/shared": "npm:3.0.0"
|
||||
"@eslint-react/var": "npm:3.0.0"
|
||||
"@typescript-eslint/scope-manager": "npm:^8.57.0"
|
||||
"@typescript-eslint/types": "npm:^8.57.0"
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
ts-pattern: "npm:^5.9.0"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/bc38ef9191dd4b8da213b86b02bce995edbbd5cd805cb89f7285eec4822c436f6978937f77a65d0fe6db90ed7657044872f945969753b184f5bcb9e1cc34c87a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-react/eslint-plugin@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@eslint-react/eslint-plugin@npm:3.0.0"
|
||||
dependencies:
|
||||
"@eslint-react/shared": "npm:3.0.0"
|
||||
"@typescript-eslint/scope-manager": "npm:^8.57.0"
|
||||
"@typescript-eslint/type-utils": "npm:^8.57.0"
|
||||
"@typescript-eslint/types": "npm:^8.57.0"
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
eslint-plugin-react-dom: "npm:3.0.0"
|
||||
eslint-plugin-react-naming-convention: "npm:3.0.0"
|
||||
eslint-plugin-react-rsc: "npm:3.0.0"
|
||||
eslint-plugin-react-web-api: "npm:3.0.0"
|
||||
eslint-plugin-react-x: "npm:3.0.0"
|
||||
ts-api-utils: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/d52bdbdf058c58149bfb104e182e05b4e88280edc440e70aede334205cad280e79829217d7fbfeda1162eb7020a5e7bb5339a9e0ac6e8c1143096d0261146ec3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-react/shared@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@eslint-react/shared@npm:3.0.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
ts-pattern: "npm:^5.9.0"
|
||||
zod: "npm:^4.3.6"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/04af79b8aca7063cd093b0447d3eaa5067d1614bde019fc5a16df308eff0460a574440223504b230c06617a1ceeaf149730ca1ba6b863dc173baf7bf0e1b1ceb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-react/var@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@eslint-react/var@npm:3.0.0"
|
||||
dependencies:
|
||||
"@eslint-react/ast": "npm:3.0.0"
|
||||
"@eslint-react/shared": "npm:3.0.0"
|
||||
"@typescript-eslint/scope-manager": "npm:^8.57.0"
|
||||
"@typescript-eslint/types": "npm:^8.57.0"
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
ts-pattern: "npm:^5.9.0"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/1dcd34e44ad14c5be3889551c8bbb8a0eadf9b05a3bee343e8640a9953b27a2eeec9c9fc4997e56a4fff0b6a512ddb663d8411cf751e988201488a4207f9573e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint/compat@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "@eslint/compat@npm:2.0.0"
|
||||
|
|
@ -4133,7 +4230,7 @@ __metadata:
|
|||
"@ai-sdk/anthropic": "npm:^3.0.58"
|
||||
"@ai-sdk/openai": "npm:^3.0.47"
|
||||
"@esm2cjs/p-queue": "npm:^7.3.0"
|
||||
"@hyperdx/common-utils": "npm:^0.16.2"
|
||||
"@hyperdx/common-utils": "npm:^0.17.0"
|
||||
"@hyperdx/node-opentelemetry": "npm:^0.9.0"
|
||||
"@hyperdx/passport-local-mongoose": "npm:^9.0.1"
|
||||
"@opentelemetry/api": "npm:^1.8.0"
|
||||
|
|
@ -4213,10 +4310,11 @@ __metadata:
|
|||
"@codemirror/lint": "npm:^6.0.0"
|
||||
"@codemirror/state": "npm:^6.0.0"
|
||||
"@dagrejs/dagre": "npm:^1.1.5"
|
||||
"@eslint-react/eslint-plugin": "npm:^3.0.0"
|
||||
"@eslint/compat": "npm:^2.0.0"
|
||||
"@hookform/resolvers": "npm:^3.9.0"
|
||||
"@hyperdx/browser": "npm:^0.22.0"
|
||||
"@hyperdx/common-utils": "npm:^0.16.2"
|
||||
"@hyperdx/common-utils": "npm:^0.17.0"
|
||||
"@hyperdx/node-opentelemetry": "npm:^0.9.0"
|
||||
"@jedmao/location": "npm:^3.0.0"
|
||||
"@mantine/core": "npm:^7.17.8"
|
||||
|
|
@ -4266,7 +4364,6 @@ __metadata:
|
|||
dayjs: "npm:^1.11.19"
|
||||
eslint-config-next: "npm:^16.0.10"
|
||||
eslint-plugin-playwright: "npm:^2.4.0"
|
||||
eslint-plugin-react: "npm:^7.37.0"
|
||||
eslint-plugin-react-hook-form: "npm:^0.3.1"
|
||||
eslint-plugin-react-hooks: "npm:^7.0.1"
|
||||
eslint-plugin-storybook: "npm:10.1.4"
|
||||
|
|
@ -4342,7 +4439,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@hyperdx/common-utils@npm:^0.16.2, @hyperdx/common-utils@workspace:packages/common-utils":
|
||||
"@hyperdx/common-utils@npm:^0.17.0, @hyperdx/common-utils@workspace:packages/common-utils":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@hyperdx/common-utils@workspace:packages/common-utils"
|
||||
dependencies:
|
||||
|
|
@ -10230,6 +10327,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.57.2":
|
||||
version: 8.57.2
|
||||
resolution: "@typescript-eslint/project-service@npm:8.57.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.57.2"
|
||||
"@typescript-eslint/types": "npm:^8.57.2"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/f84e3165b0a214318d4bc119018b87c044170d7638945e84bd4cee2d752b62c1797ce722ca1161cd06f48512d0115ef75500e6c8fc01005ad4bb39fb48dd77bf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.48.1"
|
||||
|
|
@ -10240,6 +10350,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.57.2, @typescript-eslint/scope-manager@npm:^8.57.0":
|
||||
version: 8.57.2
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.57.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.57.2"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.57.2"
|
||||
checksum: 10c0/532b1a97a5c2fce51400fa1a94e09615b4df84ce1f2d107206a3f3935074cada396a3e30f155582a698981832868e1afea1641ff779ad9456fdc94169b7def64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.48.1, @typescript-eslint/tsconfig-utils@npm:^8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.1"
|
||||
|
|
@ -10249,6 +10369,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.57.2, @typescript-eslint/tsconfig-utils@npm:^8.57.2":
|
||||
version: 8.57.2
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.57.2"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/199dad2d96efc88ce94f5f3e12e97205537bf7a7152e56ef1d84dfbe7bd1babebea9b9f396c01b6c447505a4eb02c1cbbd2c28828c587b51b41b15d017a11d2f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.48.1"
|
||||
|
|
@ -10265,6 +10394,22 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:^8.57.0":
|
||||
version: 8.57.2
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.57.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.57.2"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.57.2"
|
||||
"@typescript-eslint/utils": "npm:8.57.2"
|
||||
debug: "npm:^4.4.3"
|
||||
ts-api-utils: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/9c479cd0e809d26b7da7b31e830520bc016aaf528bc10a8b8279374808cb76a27f1b4adc77c84156417dc70f6a9e8604f47717b555a27293da2b9b5cfda70411
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.48.1, @typescript-eslint/types@npm:^8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/types@npm:8.48.1"
|
||||
|
|
@ -10272,6 +10417,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.57.2, @typescript-eslint/types@npm:^8.57.0, @typescript-eslint/types@npm:^8.57.2":
|
||||
version: 8.57.2
|
||||
resolution: "@typescript-eslint/types@npm:8.57.2"
|
||||
checksum: 10c0/3cd87dd77d28b3ac2fed56a17909b0d11633628d4d733aa148dfd7af72e2cc3ec0e6114b72fac0ff538e8a47e907b4b10dab4095170ae1bd73719ef0b8eaf2e7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.48.1"
|
||||
|
|
@ -10291,6 +10443,25 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.57.2, @typescript-eslint/typescript-estree@npm:^8.57.0":
|
||||
version: 8.57.2
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.57.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.57.2"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.57.2"
|
||||
"@typescript-eslint/types": "npm:8.57.2"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.57.2"
|
||||
debug: "npm:^4.4.3"
|
||||
minimatch: "npm:^10.2.2"
|
||||
semver: "npm:^7.7.3"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
ts-api-utils: "npm:^2.4.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/2c5d143f0abbafd07a45f0b956aab5d6487b27f74fe93bee93e0a3f8edc8913f1522faf8d7d5215f3809a8d12f5729910ea522156552f2481b66e6d05ab311ae
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.48.1, @typescript-eslint/utils@npm:^8.8.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/utils@npm:8.48.1"
|
||||
|
|
@ -10306,6 +10477,21 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.57.2, @typescript-eslint/utils@npm:^8.57.0":
|
||||
version: 8.57.2
|
||||
resolution: "@typescript-eslint/utils@npm:8.57.2"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.9.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.57.2"
|
||||
"@typescript-eslint/types": "npm:8.57.2"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.57.2"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.0.0"
|
||||
checksum: 10c0/5771f3d4206004cc817a6556a472926b4c1c885dc448049c10ffab1d5aac7bd59450a391fb57ce8ef31a8367e9c8ddb3bc9370c4e83fc8b61f50fd5189390e8f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.48.1":
|
||||
version: 8.48.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.48.1"
|
||||
|
|
@ -10316,6 +10502,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.57.2":
|
||||
version: 8.57.2
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.57.2"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.57.2"
|
||||
eslint-visitor-keys: "npm:^5.0.0"
|
||||
checksum: 10c0/8ceb8c228bf97b3e4b343bf6e42a91998d2522f459eb6b53c6bfad4898a9df74295660893dee6b698bdbbda537e968bfc13a3c56fc341089ebfba13db766a574
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@uiw/codemirror-extensions-basic-setup@npm:4.23.3":
|
||||
version: 4.23.3
|
||||
resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.3"
|
||||
|
|
@ -11825,6 +12021,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"birecord@npm:^0.1.1":
|
||||
version: 0.1.1
|
||||
resolution: "birecord@npm:0.1.1"
|
||||
checksum: 10c0/7c7f8c38caebd98bcbfc8a209eaa3044384636ea162757de670c8633b7d1064b3b58e4e3d3a48fe10e279cd39290a76a31a18956a1c76b2b13522b6cf0293238
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bl@npm:^4.0.3, bl@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "bl@npm:4.1.0"
|
||||
|
|
@ -12918,6 +13121,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"compare-versions@npm:^6.1.1":
|
||||
version: 6.1.1
|
||||
resolution: "compare-versions@npm:6.1.1"
|
||||
checksum: 10c0/415205c7627f9e4f358f571266422980c9fe2d99086be0c9a48008ef7c771f32b0fbe8e97a441ffedc3910872f917a0675fe0fe3c3b6d331cda6d8690be06338
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"component-emitter@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "component-emitter@npm:1.3.0"
|
||||
|
|
@ -15116,6 +15326,26 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-react-dom@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "eslint-plugin-react-dom@npm:3.0.0"
|
||||
dependencies:
|
||||
"@eslint-react/ast": "npm:3.0.0"
|
||||
"@eslint-react/core": "npm:3.0.0"
|
||||
"@eslint-react/shared": "npm:3.0.0"
|
||||
"@eslint-react/var": "npm:3.0.0"
|
||||
"@typescript-eslint/scope-manager": "npm:^8.57.0"
|
||||
"@typescript-eslint/types": "npm:^8.57.0"
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
compare-versions: "npm:^6.1.1"
|
||||
ts-pattern: "npm:^5.9.0"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/00b4ec055b7bc2cc12becbed6da633e2574b25d2ff5cfc7f4d74b37b96d95b04464403f7ce583b95fa530bb52af5b7d31c9ad8a35b402b24623c0c2a7490ad35
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-react-hook-form@npm:^0.3.1":
|
||||
version: 0.3.1
|
||||
resolution: "eslint-plugin-react-hook-form@npm:0.3.1"
|
||||
|
|
@ -15140,6 +15370,90 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-react-naming-convention@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "eslint-plugin-react-naming-convention@npm:3.0.0"
|
||||
dependencies:
|
||||
"@eslint-react/ast": "npm:3.0.0"
|
||||
"@eslint-react/core": "npm:3.0.0"
|
||||
"@eslint-react/shared": "npm:3.0.0"
|
||||
"@eslint-react/var": "npm:3.0.0"
|
||||
"@typescript-eslint/scope-manager": "npm:^8.57.0"
|
||||
"@typescript-eslint/type-utils": "npm:^8.57.0"
|
||||
"@typescript-eslint/types": "npm:^8.57.0"
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
compare-versions: "npm:^6.1.1"
|
||||
string-ts: "npm:^2.3.1"
|
||||
ts-pattern: "npm:^5.9.0"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/71abffdba83e5070b98c2f246577e968423a9445dde237222721ba789c69646ec8cd701d98172ef7d5f9ca98ff0383c769a1c646df81ea46f3659c0406e441c8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-react-rsc@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "eslint-plugin-react-rsc@npm:3.0.0"
|
||||
dependencies:
|
||||
"@eslint-react/ast": "npm:3.0.0"
|
||||
"@eslint-react/shared": "npm:3.0.0"
|
||||
"@eslint-react/var": "npm:3.0.0"
|
||||
"@typescript-eslint/scope-manager": "npm:^8.57.0"
|
||||
"@typescript-eslint/type-utils": "npm:^8.57.0"
|
||||
"@typescript-eslint/types": "npm:^8.57.0"
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
ts-pattern: "npm:^5.9.0"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/d129083595c805aba35c7faa753b9c76ed507b388d522cf4e0873dfabd93f50f8c350f9910c49588302ee80924ea551ec0b5725d4aa6ceb6836f0c2eb2fcc757
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-react-web-api@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "eslint-plugin-react-web-api@npm:3.0.0"
|
||||
dependencies:
|
||||
"@eslint-react/ast": "npm:3.0.0"
|
||||
"@eslint-react/core": "npm:3.0.0"
|
||||
"@eslint-react/shared": "npm:3.0.0"
|
||||
"@eslint-react/var": "npm:3.0.0"
|
||||
"@typescript-eslint/scope-manager": "npm:^8.57.0"
|
||||
"@typescript-eslint/types": "npm:^8.57.0"
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
birecord: "npm:^0.1.1"
|
||||
ts-pattern: "npm:^5.9.0"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/7cafcd210eec97f16e70ce641e8d8daf2313e643caa8121bdcee5df6b107f5764e2331d993b67e5fc896a893cbaa30db9751df94284fc92fb30205f0240ee556
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-react-x@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "eslint-plugin-react-x@npm:3.0.0"
|
||||
dependencies:
|
||||
"@eslint-react/ast": "npm:3.0.0"
|
||||
"@eslint-react/core": "npm:3.0.0"
|
||||
"@eslint-react/shared": "npm:3.0.0"
|
||||
"@eslint-react/var": "npm:3.0.0"
|
||||
"@typescript-eslint/scope-manager": "npm:^8.57.0"
|
||||
"@typescript-eslint/type-utils": "npm:^8.57.0"
|
||||
"@typescript-eslint/types": "npm:^8.57.0"
|
||||
"@typescript-eslint/utils": "npm:^8.57.0"
|
||||
compare-versions: "npm:^6.1.1"
|
||||
string-ts: "npm:^2.3.1"
|
||||
ts-api-utils: "npm:^2.4.0"
|
||||
ts-pattern: "npm:^5.9.0"
|
||||
peerDependencies:
|
||||
eslint: ^10.0.0
|
||||
typescript: "*"
|
||||
checksum: 10c0/a58af67830dd54ddf1acab60d6a78da516fee6072591b50454dbef83a5132f3e7e57d54a191e8b3a412556191f3558bbe9ed5d0fd3f77323311cd8f6a99b5ce2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-plugin-react@npm:^7.37.0":
|
||||
version: 7.37.5
|
||||
resolution: "eslint-plugin-react@npm:7.37.5"
|
||||
|
|
@ -15232,6 +15546,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint-visitor-keys@npm:^5.0.0":
|
||||
version: 5.0.1
|
||||
resolution: "eslint-visitor-keys@npm:5.0.1"
|
||||
checksum: 10c0/16190bdf2cbae40a1109384c94450c526a79b0b9c3cb21e544256ed85ac48a4b84db66b74a6561d20fe6ab77447f150d711c2ad5ad74df4fcc133736bce99678
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"eslint@npm:^9.39.1":
|
||||
version: 9.39.1
|
||||
resolution: "eslint@npm:9.39.1"
|
||||
|
|
@ -20803,6 +21124,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^10.2.2":
|
||||
version: 10.2.4
|
||||
resolution: "minimatch@npm:10.2.4"
|
||||
dependencies:
|
||||
brace-expansion: "npm:^5.0.2"
|
||||
checksum: 10c0/35f3dfb7b99b51efd46afd378486889f590e7efb10e0f6a10ba6800428cf65c9a8dedb74427d0570b318d749b543dc4e85f06d46d2858bc8cac7e1eb49a95945
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:^8.0.2":
|
||||
version: 8.0.4
|
||||
resolution: "minimatch@npm:8.0.4"
|
||||
|
|
@ -25714,6 +26044,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-ts@npm:^2.3.1":
|
||||
version: 2.3.1
|
||||
resolution: "string-ts@npm:2.3.1"
|
||||
checksum: 10c0/14b2829934713bf6cdf7ea54d948283af345e5c505bfb505aca949ab67235cbc99a3e335581f8a91f68935ac52c0d44b32112618658371e5593d5a75f26b3048
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3":
|
||||
version: 4.2.3
|
||||
resolution: "string-width@npm:4.2.3"
|
||||
|
|
@ -26811,6 +27148,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-api-utils@npm:^2.4.0":
|
||||
version: 2.5.0
|
||||
resolution: "ts-api-utils@npm:2.5.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4"
|
||||
checksum: 10c0/767849383c114e7f1971fa976b20e73ac28fd0c70d8d65c0004790bf4d8f89888c7e4cf6d5949f9c1beae9bc3c64835bef77bbe27fddf45a3c7b60cebcf85c8c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-dedent@npm:^2.0.0":
|
||||
version: 2.2.0
|
||||
resolution: "ts-dedent@npm:2.2.0"
|
||||
|
|
@ -26903,6 +27249,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-pattern@npm:^5.9.0":
|
||||
version: 5.9.0
|
||||
resolution: "ts-pattern@npm:5.9.0"
|
||||
checksum: 10c0/7640db25c39d29b287471b2b82d4f7b4674a02098c6ba4d10fed180adfb07d0e0c71930d9e59dc0d90654145e02fd320af63cf0df3c41e100d4154658a612a0a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tsc-alias@npm:^1.8.8":
|
||||
version: 1.8.8
|
||||
resolution: "tsc-alias@npm:1.8.8"
|
||||
|
|
@ -28575,7 +28928,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^4.1.11":
|
||||
"zod@npm:^4.1.11, zod@npm:^4.3.6":
|
||||
version: 4.3.6
|
||||
resolution: "zod@npm:4.3.6"
|
||||
checksum: 10c0/860d25a81ab41d33aa25f8d0d07b091a04acb426e605f396227a796e9e800c44723ed96d0f53a512b57be3d1520f45bf69c0cb3b378a232a00787a2609625307
|
||||
|
|
|
|||
Loading…
Reference in a new issue