mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-25 20:47:45 +03:00
Compare commits
1 Commits
weakpointe
...
vmui/updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e64ee71fcf |
1901
app/vmui/packages/vmui/package-lock.json
generated
1901
app/vmui/packages/vmui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1911,5 +1911,6 @@ export default {
|
||||
"zimbabwe": "🇿🇼",
|
||||
"england": "🏴",
|
||||
"scotland": "🏴",
|
||||
"wales": "🏴"
|
||||
"wales": "🏴",
|
||||
"large_purple_circle": "🟣",
|
||||
};
|
||||
|
||||
@@ -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 }));
|
||||
|
||||
41
app/vmui/packages/vmui/src/utils/marked/markedEmoji.test.ts
Normal file
41
app/vmui/packages/vmui/src/utils/marked/markedEmoji.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
66
app/vmui/packages/vmui/src/utils/marked/markedEmoji.ts
Normal file
66
app/vmui/packages/vmui/src/utils/marked/markedEmoji.ts
Normal 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;
|
||||
@@ -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 || {};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user