Compare commits

...

1 Commits

Author SHA1 Message Date
Yury Molodov
e64ee71fcf vmui: refresh dependencies, move type packages to devDeps
Signed-off-by: Yury Molodov <yurymolodov@gmail.com>
2025-07-01 14:42:03 +02:00
9 changed files with 856 additions and 1223 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -5,29 +5,20 @@
"homepage": "./",
"type": "module",
"dependencies": {
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.get": "^4.4.9",
"@types/lodash.orderBy": "^4.6.9",
"@types/lodash.throttle": "^4.1.9",
"@types/qs": "^6.9.18",
"@types/react": "^19.1.2",
"@types/react-input-mask": "^3.0.6",
"@types/react-router-dom": "^5.3.3",
"classnames": "^2.5.1",
"dayjs": "^1.11.13",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.orderBy": "^4.6.0",
"lodash.orderby": "^4.6.0",
"lodash.throttle": "^4.1.1",
"marked": "^15.0.8",
"marked-emoji": "^2.0.0",
"preact": "^10.26.5",
"marked": "^16.0.0",
"preact": "^10.26.9",
"qs": "^6.14.0",
"react-input-mask": "^2.0.4",
"react-router-dom": "^7.6.0",
"react-router-dom": "^7.6.3",
"uplot": "^1.6.32",
"vite": "^6.2.7",
"web-vitals": "^4.2.4"
"vite": "^7.0.0",
"web-vitals": "^5.0.3"
},
"scripts": {
"prestart": "npm run copy-metricsql-docs",
@@ -61,26 +52,32 @@
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.24.0",
"@preact/preset-vite": "^2.10.1",
"@eslint/js": "^9.30.0",
"@preact/preset-vite": "^2.10.2",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/preact": "^3.2.4",
"@types/node": "^22.14.1",
"@typescript-eslint/eslint-plugin": "^8.30.1",
"@typescript-eslint/parser": "^8.30.1",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.get": "^4.4.9",
"@types/lodash.orderby": "^4.6.9",
"@types/lodash.throttle": "^4.1.9",
"@types/node": "^24.0.8",
"@types/qs": "^6.14.0",
"@types/react": "^19.1.8",
"@types/react-input-mask": "^3.0.6",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.35.1",
"cross-env": "^7.0.3",
"eslint": "^9.24.0",
"eslint": "^9.30.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-unused-imports": "^4.1.4",
"globals": "^16.0.0",
"globals": "^16.3.0",
"http-proxy-middleware": "^3.0.5",
"jsdom": "^26.1.0",
"postcss": "^8.5.3",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "^1.86.3",
"sass-embedded": "^1.86.3",
"postcss": "^8.5.6",
"rollup-plugin-visualizer": "^6.0.3",
"sass-embedded": "^1.89.2",
"typescript": "^5.8.3",
"vitest": "^3.1.1",
"webpack": "^5.99.5"
"vitest": "^3.2.4"
}
}

View File

@@ -1,4 +1,4 @@
import React, { FC, useMemo, useRef } from "preact/compat";
import { FC, useMemo, useRef } from "preact/compat";
import uPlot, { AlignedData } from "uplot";
import dayjs from "dayjs";
import { DATE_TIME_FORMAT } from "../../../../constants/date";
@@ -27,7 +27,7 @@ const BarHitsTooltip: FC<Props> = ({ data, focusDataIdx, uPlotInst }) => {
const tooltipItems = values.map((value, i) => {
const targetSeries = series[i + 1];
const stroke = (targetSeries?.stroke as () => string)?.();
const label = targetSeries?.label;
const label = targetSeries?.label as string;
const show = targetSeries?.show;
return {
label,

View File

@@ -1911,5 +1911,6 @@ export default {
"zimbabwe": "🇿🇼",
"england": "🏴󠁧󠁢󠁥󠁮󠁧󠁿",
"scotland": "🏴󠁧󠁢󠁳󠁣󠁴󠁿",
"wales": "🏴󠁧󠁢󠁷󠁬󠁳󠁿"
"wales": "🏴󠁧󠁢󠁷󠁬󠁳󠁿",
"large_purple_circle": "🟣",
};

View File

@@ -1,5 +1,6 @@
import { markedEmoji } from "marked-emoji";
import markedEmoji from "../utils/marked/markedEmoji";
import { marked } from "marked";
import emojis from "./emojis";
// TODO: Dynamically import the emoji map only if the emoji parser is active
marked.use(markedEmoji({ emojis, renderer: (token) => token.emoji }));

View File

@@ -0,0 +1,41 @@
import { marked } from "marked";
import markedEmoji from "./markedEmoji";
import { describe, expect, it } from "vitest";
import emojis from "../../constants/emojis";
describe("markedEmoji plugin", () => {
marked.use(markedEmoji({ emojis, renderer: (token) => token.emoji }));
const md = (src: string) => marked(src);
it("replaces :smile: with emoji", () => {
expect(md(":smile:")).toBe("<p>😄</p>\n");
});
it("replaces multiple emojis", () => {
expect(md("Great job :thumbsup:!")).toBe("<p>Great job 👍!</p>\n");
});
it("leaves unknown emoji codes untouched", () => {
expect(md("Hello :unknown:")).toBe("<p>Hello :unknown:</p>\n");
});
it("throws when emoji list is empty", () => {
expect(() => markedEmoji({ emojis: {}, renderer: () => "" })).toThrow(
/empty/i,
);
});
it("works inside bold text", () => {
expect(md("**Bold :smile:**")).toBe("<p><strong>Bold 😄</strong></p>\n");
});
it("works inside headings", () => {
expect(md("# Heading :smile:")).toBe("<h1>Heading 😄</h1>\n");
});
it("works inside list items", () => {
const src = "- item 1 :thumbsup:\n- item 2 :smile:";
const expected = "<ul>\n<li>item 1 👍</li>\n<li>item 2 😄</li>\n</ul>\n";
expect(md(src)).toBe(expected);
});
});

View File

@@ -0,0 +1,66 @@
import { MarkedExtension, RendererThis, Tokens } from "marked";
interface EmojiToken<T> extends Tokens.Generic {
type: "emoji";
raw: string;
name: string;
emoji: T;
}
type MarkedEmojiOptions<T> = {
emojis: Record<string, T>;
renderer(token: EmojiToken<T>): string;
};
function markedEmoji<T>(options: MarkedEmojiOptions<T>): MarkedExtension {
const { emojis } = options;
if (!emojis) {
throw new Error("Must provide emojis to markedEmoji");
}
const emojiNames = Object.keys(emojis)
.map(e => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
.join("|");
if (emojiNames.length === 0) {
throw new Error("Emoji list is empty; provide at least one emoji.");
}
const emojiRegex = new RegExp(`:(${emojiNames}):`);
const tokenizerRule = new RegExp(`^${emojiRegex.source}`);
return {
extensions: [{
name: "emoji",
level: "inline",
start(src: string) {
return src.match(emojiRegex)?.index;
},
tokenizer(src: string) {
const match = tokenizerRule.exec(src);
if (!match) {
return;
}
const name = match[1];
const emoji = emojis[name];
if (!emoji) {
return;
}
return {
type: "emoji",
raw: match[0],
name,
emoji,
};
},
renderer(this: RendererThis, token: Tokens.Generic): string {
return options.renderer(token as EmojiToken<T>);
}
}],
};
}
export default markedEmoji;

View File

@@ -54,9 +54,9 @@ export const getDashLine = (group: number): number[] => {
return group <= 1 ? [] : [group*4, group*1.2];
};
export const getMetricName = (metricItem: MetricResult, seriesItem: SeriesItem) => {
export const getMetricName = (metricItem: MetricResult, seriesItem: SeriesItem): string => {
if (seriesItem?.hasAlias && seriesItem?.label) {
return seriesItem.label;
return seriesItem.label as string;
}
const metric = metricItem?.metric || {};

View File

@@ -84,7 +84,7 @@ const getSeriesStatistics = (d: MetricResult) => {
export const getLegendItem = (s: SeriesItem, group: number): LegendItemType => ({
group,
label: s.label || "",
label: (s.label || "") as string,
color: s.stroke as string,
checked: s.show || false,
freeFormFields: s.freeFormFields,
@@ -96,7 +96,7 @@ export const getLegendItem = (s: SeriesItem, group: number): LegendItemType => (
export const getHideSeries = ({ hideSeries, legend, metaKey, series, isAnomalyView }: HideSeriesArgs): string[] => {
const { label } = legend;
const include = includesHideSeries(label, hideSeries);
const labels = series.map(s => s.label || "");
const labels = series.map(s => s.label || "") as string[];
// if anomalyView is true, always return all series except the one specified by `label`
if (isAnomalyView) {