mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
chore: Update to next 16, react 19, add react compiler (#1434)
fixes: HDX-2956 Co-authored-by: Brandon Pereira <7552738+brandon-pereira@users.noreply.github.com>
This commit is contained in:
parent
4b1557d957
commit
52d2798582
63 changed files with 3710 additions and 5959 deletions
7
.changeset/fair-points-float.md
Normal file
7
.changeset/fair-points-float.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
"@hyperdx/common-utils": minor
|
||||
"@hyperdx/api": minor
|
||||
"@hyperdx/app": minor
|
||||
---
|
||||
|
||||
chore: Upgrade nextjs, react, and eslint + add react compiler
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -34,6 +34,7 @@ packages/app/.pnp.js
|
|||
packages/app/.vercel
|
||||
packages/app/coverage
|
||||
packages/app/out
|
||||
packages/app/next-env.d.ts
|
||||
|
||||
# optional npm cache directory
|
||||
**/.npm
|
||||
|
|
|
|||
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
|||
v22.16.0
|
||||
22.21.1
|
||||
|
|
|
|||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
|
@ -35,6 +35,13 @@
|
|||
"**/yarn.lock": true,
|
||||
"**/yarn-*.cjs": true
|
||||
},
|
||||
"cSpell.words": ["micropip", "opamp", "pyimport", "pyodide"],
|
||||
"cSpell.words": [
|
||||
"clickhouse",
|
||||
"hyperdx",
|
||||
"micropip",
|
||||
"opamp",
|
||||
"pyimport",
|
||||
"pyodide"
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ yarn run lint
|
|||
|
||||
## File Locations Quick Reference
|
||||
|
||||
- **Config**: `packages/api/src/config.ts`, `packages/app/next.config.js`, `docker-compose.dev.yml`
|
||||
- **Config**: `packages/api/src/config.ts`, `packages/app/next.config.mjs`, `docker-compose.dev.yml`
|
||||
- **Models**: `packages/api/src/models/`
|
||||
- **API Routes**: `packages/api/src/routers/`
|
||||
- **Controllers**: `packages/api/src/controllers/`
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ COPY .yarn ./.yarn
|
|||
COPY .yarnrc.yml yarn.lock package.json nx.json .prettierrc .prettierignore ./tsconfig.base.json ./
|
||||
COPY ./packages/common-utils ./packages/common-utils
|
||||
COPY ./packages/api/jest.config.js ./packages/api/tsconfig.json ./packages/api/tsconfig.build.json ./packages/api/package.json ./packages/api/
|
||||
COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.js ./packages/app/mdx.d.ts ./packages/app/.eslintrc.js ./packages/app/
|
||||
COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.mjs ./packages/app/mdx.d.ts ./packages/app/eslint.config.mjs ./packages/app/
|
||||
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
|
|
|||
19
nx.json
19
nx.json
|
|
@ -8,35 +8,26 @@
|
|||
},
|
||||
"targetDefaults": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
"dependsOn": ["^build"]
|
||||
}
|
||||
},
|
||||
"tasksRunnerOptions": {
|
||||
"default": {
|
||||
"runner": "nx/tasks-runners/default",
|
||||
"options": {
|
||||
"cacheableOperations": [
|
||||
"build",
|
||||
"lint",
|
||||
"test"
|
||||
]
|
||||
"cacheableOperations": ["build", "lint", "test"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"namedInputs": {
|
||||
"default": [
|
||||
"{projectRoot}/**/*",
|
||||
"sharedGlobals"
|
||||
],
|
||||
"default": ["{projectRoot}/**/*", "sharedGlobals"],
|
||||
"production": [
|
||||
"default",
|
||||
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
|
||||
"!{projectRoot}/tsconfig.spec.json",
|
||||
"!{projectRoot}/jest.config.[jt]s",
|
||||
"!{projectRoot}/.eslintrc.json"
|
||||
"!{projectRoot}/eslint.config.mjs"
|
||||
],
|
||||
"sharedGlobals": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
package.json
18
package.json
|
|
@ -9,17 +9,19 @@
|
|||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.2",
|
||||
"@nx/workspace": "21.3.11",
|
||||
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
||||
"@typescript-eslint/parser": "^8.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"dotenv": "^16.4.7",
|
||||
"dotenv-cli": "^8.0.0",
|
||||
"dotenv-expand": "^12.0.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "13",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-next": "16.0.7",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-n": "^16.4.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-security": "^2.1.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"husky": "^8.0.3",
|
||||
|
|
@ -43,12 +45,12 @@
|
|||
"lint-staged": {
|
||||
"packages/api/src/routers/external-api/**/*.ts": [
|
||||
"prettier --write --ignore-unknown",
|
||||
"eslint --fix --quiet",
|
||||
"eslint --flag v10_config_lookup_from_file --fix --quiet",
|
||||
"sh -c 'cd packages/api && yarn run docgen && git add openapi.json'"
|
||||
],
|
||||
"**/*.{ts,tsx}": [
|
||||
"prettier --write --ignore-unknown",
|
||||
"eslint --fix --quiet"
|
||||
"eslint --flag v10_config_lookup_from_file --fix --quiet"
|
||||
],
|
||||
"**/*.{mdx,json,yml}": [
|
||||
"prettier --write --ignore-unknown"
|
||||
|
|
@ -56,8 +58,8 @@
|
|||
},
|
||||
"packageManager": "yarn@4.5.1",
|
||||
"resolutions": {
|
||||
"@types/react": "18.3.1",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/react": "19.0.7",
|
||||
"@types/react-dom": "19.0.3",
|
||||
"@types/express": "4.17.21",
|
||||
"@types/express-serve-static-core": "4.17.43"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
keys
|
||||
node_modules
|
||||
archive
|
||||
migrate-mongo-config.ts
|
||||
jest.setup.ts
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
ignorePatterns: ['migrate-mongo-config.ts'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint', 'prettier', 'simple-import-sort'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:n/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:security/recommended-legacy',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-empty-interface': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-namespace': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'n/no-process-exit': 'warn',
|
||||
'n/no-missing-import': 'off',
|
||||
'n/no-unpublished-import': [
|
||||
'error',
|
||||
{
|
||||
allowModules: ['mongodb', 'supertest'],
|
||||
},
|
||||
],
|
||||
'n/no-unsupported-features/es-syntax': [
|
||||
'error',
|
||||
{
|
||||
ignores: ['modules'],
|
||||
},
|
||||
],
|
||||
'prettier/prettier': 'error',
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
},
|
||||
};
|
||||
99
packages/api/eslint.config.mjs
Normal file
99
packages/api/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import js from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import prettierConfig from 'eslint-config-prettier';
|
||||
import prettierPlugin from 'eslint-plugin-prettier';
|
||||
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
||||
import nodePlugin from 'eslint-plugin-n';
|
||||
import securityPlugin from 'eslint-plugin-security';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
prettierConfig,
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'build/**',
|
||||
'dist/**',
|
||||
'coverage/**',
|
||||
'src/coverage/**',
|
||||
'migrations/**',
|
||||
'migrate-mongo-config.ts',
|
||||
'**/*.config.js',
|
||||
'**/*.config.mjs',
|
||||
'jest.config.js',
|
||||
'jest.setup.ts',
|
||||
],
|
||||
},
|
||||
{
|
||||
files: ['src/**/*.ts', '!migrations/**'],
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint.plugin,
|
||||
'simple-import-sort': simpleImportSort,
|
||||
'prettier': prettierPlugin,
|
||||
'n': nodePlugin,
|
||||
'security': securityPlugin,
|
||||
},
|
||||
rules: {
|
||||
...nodePlugin.configs.recommended.rules,
|
||||
...securityPlugin.configs['recommended-legacy'].rules,
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-empty-interface': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-namespace': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'n/no-process-exit': 'warn',
|
||||
'n/no-missing-import': 'off',
|
||||
'n/no-unpublished-import': [
|
||||
'error',
|
||||
{
|
||||
allowModules: ['mongodb', 'supertest'],
|
||||
},
|
||||
],
|
||||
'n/no-unsupported-features/es-syntax': [
|
||||
'error',
|
||||
{
|
||||
ignores: ['modules'],
|
||||
},
|
||||
],
|
||||
'prettier/prettier': 'error',
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
},
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
globals: {
|
||||
console: 'readonly',
|
||||
process: 'readonly',
|
||||
module: 'readonly',
|
||||
require: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
__filename: 'readonly',
|
||||
exports: 'readonly',
|
||||
Buffer: 'readonly',
|
||||
setTimeout: 'readonly',
|
||||
clearTimeout: 'readonly',
|
||||
setInterval: 'readonly',
|
||||
clearInterval: 'readonly',
|
||||
setImmediate: 'readonly',
|
||||
clearImmediate: 'readonly',
|
||||
global: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -2,12 +2,12 @@ import { Db, MongoClient } from 'mongodb';
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
module.exports = {
|
||||
async up(db: Db, client: MongoClient) {
|
||||
async up(db: Db, _client: MongoClient) {
|
||||
await db
|
||||
.collection('users')
|
||||
.updateMany({}, { $set: { accessKey: uuidv4() } });
|
||||
},
|
||||
async down(db: Db, client: MongoClient) {
|
||||
async down(db: Db, _client: MongoClient) {
|
||||
await db.collection('users').updateMany({}, { $unset: { accessKey: '' } });
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: [
|
||||
'simple-import-sort',
|
||||
'@typescript-eslint',
|
||||
'prettier',
|
||||
'react-hooks',
|
||||
],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'next',
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unsafe-function-type': 'warn',
|
||||
'@typescript-eslint/no-unused-expressions': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'react/display-name': 'off',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'simple-import-sort/imports': 'error',
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
|
||||
rules: {
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
{
|
||||
groups: [
|
||||
['^react$', '^next', '^[a-z]', '^@'],
|
||||
['^@/'],
|
||||
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
|
||||
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
|
||||
['^.+\\.s?css$'],
|
||||
['^\\u0000'],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Disable strict rules for E2E test files
|
||||
files: ['tests/e2e/**/*.ts', 'tests/e2e/**/*.js'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-empty': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
import { join, dirname } from 'path';
|
||||
// This file has been automatically migrated to valid ESM format by Storybook.
|
||||
import { createRequire } from 'node:module';
|
||||
import { dirname, join } from 'path';
|
||||
import type { StorybookConfig } from '@storybook/nextjs';
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
function getAbsolutePath(value: string): any {
|
||||
return dirname(require.resolve(join(value, 'package.json')));
|
||||
}
|
||||
|
|
@ -9,9 +13,8 @@ const config: StorybookConfig = {
|
|||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: [
|
||||
getAbsolutePath('@storybook/addon-links'),
|
||||
getAbsolutePath('@storybook/addon-essentials'),
|
||||
getAbsolutePath('@storybook/addon-interactions'),
|
||||
getAbsolutePath('@storybook/addon-styling-webpack'),
|
||||
getAbsolutePath('@storybook/addon-docs'),
|
||||
],
|
||||
framework: {
|
||||
name: getAbsolutePath('@storybook/nextjs'),
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
import React from 'react';
|
||||
import type { Preview } from '@storybook/react';
|
||||
import { NextAdapter } from 'next-query-params';
|
||||
import { initialize, mswLoader } from 'msw-storybook-addon';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import { NextAdapter } from 'next-query-params';
|
||||
import type { Preview } from '@storybook/nextjs';
|
||||
|
||||
import { meHandler } from '../src/mocks/handlers';
|
||||
import { ThemeWrapper } from '../src/ThemeWrapper';
|
||||
|
||||
import '@mantine/core/styles.css';
|
||||
import '@mantine/notifications/styles.css';
|
||||
import '@mantine/dates/styles.css';
|
||||
import '@mantine/dropzone/styles.css';
|
||||
|
||||
import '../styles/globals.css';
|
||||
import '../styles/app.scss';
|
||||
|
||||
import { meHandler } from '../src/mocks/handlers';
|
||||
import { ThemeWrapper } from '../src/ThemeWrapper';
|
||||
|
||||
export const parameters = {
|
||||
layout: 'fullscreen',
|
||||
options: {
|
||||
|
|
@ -61,7 +60,7 @@ const preview: Preview = {
|
|||
msw: {
|
||||
handlers: [meHandler],
|
||||
},
|
||||
backgrounds: { disable: true },
|
||||
backgrounds: { disabled: true },
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ WORKDIR /app
|
|||
COPY .yarn ./.yarn
|
||||
COPY .yarnrc.yml yarn.lock package.json nx.json .prettierrc .prettierignore ./tsconfig.base.json ./
|
||||
COPY ./packages/common-utils ./packages/common-utils
|
||||
COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.js ./packages/app/mdx.d.ts ./packages/app/.eslintrc.js ./packages/app/
|
||||
COPY ./packages/app/jest.config.js ./packages/app/tsconfig.json ./packages/app/tsconfig.build.json ./packages/app/package.json ./packages/app/next.config.mjs ./packages/app/mdx.d.ts ./packages/app/eslint.config.mjs ./packages/app/
|
||||
RUN yarn install --mode=skip-build && yarn cache clean
|
||||
|
||||
|
||||
|
|
@ -51,8 +51,8 @@ ENV NODE_ENV production
|
|||
RUN addgroup -g 1001 -S nodejs
|
||||
RUN adduser -S nextjs -u 1001
|
||||
|
||||
# You only need to copy next.config.js if you are NOT using the default configuration
|
||||
COPY --from=builder /app/packages/app/next.config.js ./
|
||||
# You only need to copy next.config.mjs if you are NOT using the default configuration
|
||||
COPY --from=builder /app/packages/app/next.config.mjs ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/packages/app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/packages/app/.next ./.next
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
|
|
|
|||
114
packages/app/eslint.config.mjs
Normal file
114
packages/app/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
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 reactHooksPlugin from 'eslint-plugin-react-hooks';
|
||||
import prettierConfig from 'eslint-config-prettier';
|
||||
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
prettierConfig,
|
||||
{
|
||||
ignores: [
|
||||
'playwright-report/**',
|
||||
'.next/**',
|
||||
'node_modules/**',
|
||||
'out/**',
|
||||
'build/**',
|
||||
'coverage/**',
|
||||
'dist/**',
|
||||
'**/*.config.js',
|
||||
'**/*.config.ts',
|
||||
'**/*.config.cjs',
|
||||
'**/*.config.mjs',
|
||||
'eslint.config.mjs',
|
||||
'public/__ENV.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||
plugins: {
|
||||
'@next/next': nextPlugin,
|
||||
react: reactPlugin,
|
||||
'react-hooks': reactHooksPlugin,
|
||||
'simple-import-sort': simpleImportSort,
|
||||
},
|
||||
rules: {
|
||||
...nextPlugin.configs.recommended.rules,
|
||||
...nextPlugin.configs['core-web-vitals'].rules,
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-empty-function': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unsafe-function-type': 'warn',
|
||||
'@typescript-eslint/no-unused-expressions': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'react/display-name': 'off',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'simple-import-sort/imports': [
|
||||
'error',
|
||||
{
|
||||
groups: [
|
||||
['^react$', '^next', '^[a-z]', '^@'],
|
||||
['^@/'],
|
||||
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
|
||||
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
|
||||
['^.+\\.s?css$'],
|
||||
['^\\u0000'],
|
||||
],
|
||||
},
|
||||
],
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||||
},
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
globals: {
|
||||
React: 'readonly',
|
||||
JSX: 'readonly',
|
||||
NodeJS: 'readonly',
|
||||
window: 'readonly',
|
||||
document: 'readonly',
|
||||
navigator: 'readonly',
|
||||
console: 'readonly',
|
||||
setTimeout: 'readonly',
|
||||
clearTimeout: 'readonly',
|
||||
setInterval: 'readonly',
|
||||
clearInterval: 'readonly',
|
||||
fetch: 'readonly',
|
||||
process: 'readonly',
|
||||
module: 'readonly',
|
||||
require: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
__filename: 'readonly',
|
||||
exports: 'readonly',
|
||||
Buffer: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['tests/e2e/**/*.{ts,js}'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-empty': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
},
|
||||
},
|
||||
...storybook.configs['flat/recommended'],
|
||||
];
|
||||
5
packages/app/next-env.d.ts
vendored
5
packages/app/next-env.d.ts
vendored
|
|
@ -1,5 +0,0 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
const { configureRuntimeEnv } = require('next-runtime-env/build/configure');
|
||||
const { version } = require('./package.json');
|
||||
|
||||
configureRuntimeEnv();
|
||||
|
||||
const withNextra = require('nextra')({
|
||||
theme: 'nextra-theme-docs',
|
||||
themeConfig: './src/nextra.config.tsx',
|
||||
});
|
||||
|
||||
const basePath = process.env.NEXT_PUBLIC_HYPERDX_BASE_PATH;
|
||||
|
||||
module.exports = {
|
||||
basePath: basePath,
|
||||
experimental: {
|
||||
instrumentationHook: true,
|
||||
// External packages to prevent bundling issues with Next.js 14
|
||||
// https://github.com/open-telemetry/opentelemetry-js/issues/4297#issuecomment-2285070503
|
||||
serverComponentsExternalPackages: [
|
||||
'@opentelemetry/instrumentation',
|
||||
'@opentelemetry/sdk-node',
|
||||
'@opentelemetry/auto-instrumentations-node',
|
||||
'@hyperdx/node-opentelemetry',
|
||||
'@hyperdx/instrumentation-sentry-node',
|
||||
],
|
||||
},
|
||||
typescript: {
|
||||
tsconfigPath: 'tsconfig.build.json',
|
||||
},
|
||||
// Ignore otel pkgs warnings
|
||||
// https://github.com/open-telemetry/opentelemetry-js/issues/4173#issuecomment-1822938936
|
||||
webpack: (
|
||||
config,
|
||||
{ buildId, dev, isServer, defaultLoaders, nextRuntime, webpack },
|
||||
) => {
|
||||
if (isServer) {
|
||||
config.ignoreWarnings = [{ module: /opentelemetry/ }];
|
||||
}
|
||||
return config;
|
||||
},
|
||||
...withNextra({
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/(.*)?', // Matches all pages
|
||||
headers: [
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'DENY',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
// This slows down builds by 2x for some reason...
|
||||
swcMinify: false,
|
||||
publicRuntimeConfig: {
|
||||
version,
|
||||
},
|
||||
productionBrowserSourceMaps: false,
|
||||
...(process.env.NEXT_OUTPUT_STANDALONE === 'true'
|
||||
? {
|
||||
output: 'standalone',
|
||||
}
|
||||
: {}),
|
||||
}),
|
||||
};
|
||||
84
packages/app/next.config.mjs
Normal file
84
packages/app/next.config.mjs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import { configureRuntimeEnv } from 'next-runtime-env/build/configure.js';
|
||||
import nextra from 'nextra';
|
||||
import { readFileSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Read version from package.json
|
||||
const packageJson = JSON.parse(
|
||||
readFileSync(join(__dirname, 'package.json'), 'utf-8')
|
||||
);
|
||||
const { version } = packageJson;
|
||||
|
||||
// Support legacy consumers of next-runtime-env that expect this value under window.__ENV
|
||||
process.env.NEXT_PUBLIC_APP_VERSION = version;
|
||||
|
||||
configureRuntimeEnv();
|
||||
|
||||
const withNextra = nextra({
|
||||
theme: 'nextra-theme-docs',
|
||||
themeConfig: './src/nextra.config.tsx',
|
||||
});
|
||||
|
||||
const basePath = process.env.NEXT_PUBLIC_HYPERDX_BASE_PATH;
|
||||
|
||||
const nextConfig = {
|
||||
reactCompiler: true,
|
||||
basePath: basePath,
|
||||
env: {
|
||||
// Ensures bundler-time replacements for client/server code that references this env var
|
||||
NEXT_PUBLIC_APP_VERSION: version,
|
||||
},
|
||||
// External packages to prevent bundling issues (moved from experimental in Next.js 15+)
|
||||
// https://github.com/open-telemetry/opentelemetry-js/issues/4297#issuecomment-2285070503
|
||||
serverExternalPackages: [
|
||||
'@opentelemetry/instrumentation',
|
||||
'@opentelemetry/sdk-node',
|
||||
'@opentelemetry/auto-instrumentations-node',
|
||||
'@hyperdx/node-opentelemetry',
|
||||
'@hyperdx/instrumentation-sentry-node',
|
||||
],
|
||||
typescript: {
|
||||
tsconfigPath: 'tsconfig.build.json',
|
||||
},
|
||||
// NOTE: Using Webpack instead of Turbopack (Next.js 16 default)
|
||||
// Reason: Turbopack has CSS module parsing issues with nested :global syntax
|
||||
// used in styles/SearchPage.module.scss and other SCSS files.
|
||||
// The --webpack flag is added to dev and build scripts in package.json.
|
||||
// TODO: Re-evaluate when Turbopack CSS module support improves
|
||||
// Ignore otel pkgs warnings
|
||||
// https://github.com/open-telemetry/opentelemetry-js/issues/4173#issuecomment-1822938936
|
||||
webpack: (config, { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }) => {
|
||||
if (isServer) {
|
||||
config.ignoreWarnings = [{ module: /opentelemetry/ }];
|
||||
}
|
||||
return config;
|
||||
},
|
||||
...withNextra({
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/(.*)?', // Matches all pages
|
||||
headers: [
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'DENY',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
productionBrowserSourceMaps: false,
|
||||
...(process.env.NEXT_OUTPUT_STANDALONE === 'true'
|
||||
? {
|
||||
output: 'standalone',
|
||||
}
|
||||
: {}),
|
||||
}),
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
|
|
@ -7,9 +7,9 @@
|
|||
"node": ">=22.16.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npx dotenv -e .env.development -- next dev",
|
||||
"dev:local": "NEXT_PUBLIC_IS_LOCAL_MODE=true npx dotenv -e .env.development -- next dev",
|
||||
"build": "next build",
|
||||
"dev": "npx dotenv -e .env.development -- next dev --webpack",
|
||||
"dev:local": "NEXT_PUBLIC_IS_LOCAL_MODE=true npx dotenv -e .env.development -- next dev --webpack",
|
||||
"build": "next build --webpack",
|
||||
"start": "next start",
|
||||
"lint": "npx eslint --quiet . --ext .ts,.tsx",
|
||||
"lint:fix": "npx eslint . --ext .ts,.tsx --fix",
|
||||
|
|
@ -33,13 +33,13 @@
|
|||
"@hyperdx/common-utils": "^0.8.0",
|
||||
"@hyperdx/node-opentelemetry": "^0.9.0",
|
||||
"@lezer/highlight": "^1.2.0",
|
||||
"@mantine/core": "7.9.2",
|
||||
"@mantine/dates": "^7.11.2",
|
||||
"@mantine/dropzone": "^8.3.1",
|
||||
"@mantine/form": "^7.11.2",
|
||||
"@mantine/hooks": "7.9.2",
|
||||
"@mantine/notifications": "^7.9.2",
|
||||
"@mantine/spotlight": "7.9.2",
|
||||
"@mantine/core": "^7.17.8",
|
||||
"@mantine/dates": "^7.17.8",
|
||||
"@mantine/dropzone": "^7.17.8",
|
||||
"@mantine/form": "^7.17.8",
|
||||
"@mantine/hooks": "^7.17.8",
|
||||
"@mantine/notifications": "^7.17.8",
|
||||
"@mantine/spotlight": "^7.17.8",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@tabler/icons-react": "^3.5.0",
|
||||
"@tanstack/react-query": "^5.56.2",
|
||||
|
|
@ -56,6 +56,7 @@
|
|||
"crypto-randomuuid": "^1.0.0",
|
||||
"date-fns": "^2.28.0",
|
||||
"date-fns-tz": "^2.0.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"flat": "^5.0.2",
|
||||
"fuse.js": "^6.6.2",
|
||||
"http-proxy-middleware": "^3.0.5",
|
||||
|
|
@ -65,7 +66,7 @@
|
|||
"ky-universal": "^0.10.1",
|
||||
"lodash": "^4.17.21",
|
||||
"ms": "^2.1.3",
|
||||
"next": "^14.2.32",
|
||||
"next": "^16.0.7",
|
||||
"next-query-params": "^4.3.1",
|
||||
"next-runtime-env": "1",
|
||||
"next-seo": "^4.28.1",
|
||||
|
|
@ -74,16 +75,16 @@
|
|||
"numbro": "^2.4.0",
|
||||
"nuqs": "1.17.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"react": "18.3.1",
|
||||
"react": "^19.2.1",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "18.3.1",
|
||||
"react-dom": "^19.2.1",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-hook-form": "^7.43.8",
|
||||
"react-hook-form-mantine": "^3.1.3",
|
||||
"react-hotkeys-hook": "^4.3.7",
|
||||
"react-json-tree": "^0.17.0",
|
||||
"react-markdown": "^8.0.4",
|
||||
"react-json-tree": "^0.20.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-papaparse": "^4.4.0",
|
||||
"react-query": "^3.39.3",
|
||||
"react-sortable-hoc": "^2.0.0",
|
||||
|
|
@ -103,19 +104,16 @@
|
|||
"zod": "3.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chromatic-com/storybook": "^1.5.0",
|
||||
"@chromatic-com/storybook": "^4.1.3",
|
||||
"@hookform/devtools": "^4.3.1",
|
||||
"@jedmao/location": "^3.0.0",
|
||||
"@playwright/test": "^1.47.0",
|
||||
"@storybook/addon-essentials": "^8.1.5",
|
||||
"@storybook/addon-interactions": "^8.1.5",
|
||||
"@storybook/addon-links": "^8.1.5",
|
||||
"@storybook/addon-styling-webpack": "^1.0.0",
|
||||
"@storybook/addon-themes": "^10.0.8",
|
||||
"@storybook/blocks": "^8.1.5",
|
||||
"@storybook/nextjs": "^8.1.5",
|
||||
"@storybook/react": "^8.1.5",
|
||||
"@storybook/test": "^8.1.5",
|
||||
"@storybook/addon-docs": "^10.1.4",
|
||||
"@storybook/addon-links": "^10.1.4",
|
||||
"@storybook/addon-styling-webpack": "^3.0.0",
|
||||
"@storybook/addon-themes": "^10.1.4",
|
||||
"@storybook/nextjs": "^10.1.4",
|
||||
"@storybook/react": "^10.1.4",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
|
|
@ -129,14 +127,15 @@
|
|||
"@types/ms": "^0.7.31",
|
||||
"@types/object-hash": "^2.2.1",
|
||||
"@types/pluralize": "^0.0.29",
|
||||
"@types/react": "18.3.1",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-copy-to-clipboard": "^5.0.2",
|
||||
"@types/react-dom": "18.3.1",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-grid-layout": "^1.3.2",
|
||||
"@types/react-syntax-highlighter": "^13.5.2",
|
||||
"@types/react-table": "^7.7.14",
|
||||
"@types/sqlstring": "^2.3.2",
|
||||
"eslint-config-next": "^14.2.29",
|
||||
"eslint-config-next": "^16.0.7",
|
||||
"eslint-plugin-storybook": "10.1.4",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^30.2.0",
|
||||
"jest-environment-jsdom": "^30.2.0",
|
||||
|
|
@ -146,7 +145,7 @@
|
|||
"postcss": "^8.4.38",
|
||||
"postcss-preset-mantine": "^1.15.0",
|
||||
"prettier": "^3.3.2",
|
||||
"storybook": "^8.1.5",
|
||||
"storybook": "^10.1.4",
|
||||
"stylelint": "^16.6.1",
|
||||
"stylelint-config-standard-scss": "^13.1.0",
|
||||
"stylelint-prettier": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ export default function Document() {
|
|||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
|
||||
<script src="/__ENV.js" />
|
||||
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
|
||||
<script src="https://cdn.jsdelivr.net/pyodide/v0.27.2/full/pyodide.js"></script>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ import { useUserPreferences } from '@/useUserPreferences';
|
|||
|
||||
import packageJson from '../package.json';
|
||||
|
||||
// Expose the same value Next injected at build time; fall back to package.json for dev tooling
|
||||
const APP_VERSION =
|
||||
process.env.NEXT_PUBLIC_APP_VERSION ?? packageJson.version ?? 'dev';
|
||||
|
||||
import api from './api';
|
||||
import {
|
||||
AppNavCloudBanner,
|
||||
|
|
@ -154,7 +158,9 @@ function SearchInput({
|
|||
data-testid="nav-search-input"
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={e => onChange(e.currentTarget.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChange(e.currentTarget.value)
|
||||
}
|
||||
leftSection={<i className="bi bi-search fs-8 ps-1 " />}
|
||||
onKeyDown={handleKeyDown}
|
||||
rightSection={
|
||||
|
|
@ -880,7 +886,7 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
|
|||
}}
|
||||
>
|
||||
<AppNavHelpMenu
|
||||
version={packageJson.version}
|
||||
version={APP_VERSION}
|
||||
onAddDataClick={openInstallInstructions}
|
||||
/>
|
||||
<AppNavUserMenu
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default function AutocompleteInput({
|
|||
queryHistoryType,
|
||||
'data-testid': dataTestId,
|
||||
}: {
|
||||
inputRef: React.RefObject<HTMLTextAreaElement>;
|
||||
inputRef: React.RefObject<HTMLTextAreaElement | null>;
|
||||
value?: string;
|
||||
onChange: (value: string) => void;
|
||||
onSubmit?: () => void;
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ function DBChartExplorerPage() {
|
|||
// showRelativeInterval: isLive,
|
||||
});
|
||||
|
||||
const submitRef = useRef<() => void>();
|
||||
const submitRef = useRef<(() => void) | undefined>(undefined);
|
||||
const { data: sources } = useSources();
|
||||
const { data: me } = api.useMe();
|
||||
|
||||
|
|
|
|||
|
|
@ -469,7 +469,9 @@ function DashboardName({
|
|||
<Input
|
||||
type="text"
|
||||
value={editedName}
|
||||
onChange={e => setEditedName(e.target.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setEditedName(e.target.value)
|
||||
}
|
||||
placeholder="Dashboard Name"
|
||||
/>
|
||||
<Button ms="sm" variant="outline" type="submit" color="green">
|
||||
|
|
@ -697,9 +699,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) {
|
|||
dateRange={searchedTimeRange}
|
||||
onEditClick={() => setEditedTile(chart)}
|
||||
granularity={
|
||||
isRefreshEnabled
|
||||
? granularityOverride
|
||||
: (granularity ?? undefined)
|
||||
isRefreshEnabled ? granularityOverride : granularity ?? undefined
|
||||
}
|
||||
filters={[
|
||||
{
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ function SaveSearchModal({
|
|||
<ActionIcon
|
||||
variant="transparent"
|
||||
color="gray"
|
||||
onClick={e => {
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setTags(tags.filter(t => t !== tag));
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import type { Meta } from '@storybook/nextjs';
|
||||
|
||||
import { TooltipItem } from './HDXMultiSeriesTimeChart';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Meta } from '@storybook/react';
|
||||
import type { Meta } from '@storybook/nextjs';
|
||||
|
||||
import { NetworkBody } from './LogSidePanelElements';
|
||||
|
||||
|
|
|
|||
|
|
@ -1310,9 +1310,14 @@ function ServicesDashboardPage() {
|
|||
|
||||
// Auto submit when service or source changes
|
||||
useEffect(() => {
|
||||
const normalizedService = service ?? '';
|
||||
const appliedService = appliedConfig.service ?? '';
|
||||
const normalizedSource = sourceId ?? '';
|
||||
const appliedSource = appliedConfig.source ?? '';
|
||||
|
||||
if (
|
||||
service !== appliedConfig.service ||
|
||||
sourceId !== appliedConfig.source
|
||||
normalizedService !== appliedService ||
|
||||
(normalizedSource && normalizedSource !== appliedSource)
|
||||
) {
|
||||
onSubmit();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ function TimelineMouseCursor({
|
|||
xPerc,
|
||||
setXPerc,
|
||||
}: {
|
||||
containerRef: RefObject<HTMLDivElement>;
|
||||
containerRef: RefObject<HTMLDivElement | null>;
|
||||
maxVal: number;
|
||||
labelWidth: number;
|
||||
height: number;
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ export const UserPreferencesModal = ({
|
|||
placeholder="https:// or data:"
|
||||
value={userPreferences.backgroundUrl}
|
||||
leftSection={<i className="bi bi-globe" />}
|
||||
onChange={e =>
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setUserPreference({
|
||||
backgroundUrl: e.currentTarget.value,
|
||||
})
|
||||
|
|
|
|||
4
packages/app/src/__mocks__/react-json-tree.tsx
Normal file
4
packages/app/src/__mocks__/react-json-tree.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Mock for react-json-tree ESM module. We could improve this by mocking the actual module if needed.
|
||||
const JSONTree = <div data-testid="json-tree">JSONTree</div>;
|
||||
|
||||
module.exports = JSONTree;
|
||||
|
|
@ -34,14 +34,18 @@ export const AlertPreviewChart = ({
|
|||
thresholdType,
|
||||
select,
|
||||
}: AlertPreviewChartProps) => {
|
||||
const resolvedSelect =
|
||||
(select && select.trim().length > 0
|
||||
? select
|
||||
: source.defaultTableSelectExpression) ?? '';
|
||||
|
||||
const { data: aliasMap } = useAliasMapFromChartConfig({
|
||||
select: select || '',
|
||||
select: resolvedSelect,
|
||||
where: where || '',
|
||||
connection: source.connection,
|
||||
from: source.from,
|
||||
whereLanguage: whereLanguage || undefined,
|
||||
});
|
||||
|
||||
const aliasWith = Object.entries(aliasMap ?? {}).map(([key, value]) => ({
|
||||
name: key,
|
||||
sql: {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs';
|
||||
|
||||
import { ColorSwatchInput } from './ColorSwatchInput';
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
useFieldArray,
|
||||
useForm,
|
||||
UseFormSetValue,
|
||||
UseFormWatch,
|
||||
useWatch,
|
||||
} from 'react-hook-form';
|
||||
import { NativeSelect, NumberInput } from 'react-hook-form-mantine';
|
||||
import z from 'zod';
|
||||
|
|
@ -18,9 +18,11 @@ import {
|
|||
DateRange,
|
||||
DisplayType,
|
||||
Filter,
|
||||
MetricsDataType,
|
||||
SavedChartConfig,
|
||||
SelectList,
|
||||
SourceKind,
|
||||
TSource,
|
||||
} from '@hyperdx/common-utils/dist/types';
|
||||
import {
|
||||
Accordion,
|
||||
|
|
@ -68,6 +70,7 @@ import {
|
|||
} from '@/utils/alerts';
|
||||
|
||||
import HDXMarkdownChart from '../HDXMarkdownChart';
|
||||
import type { NumberFormat } from '../types';
|
||||
|
||||
import { AggFnSelectControlled } from './AggFnSelect';
|
||||
import DBNumberChart from './DBNumberChart';
|
||||
|
|
@ -106,7 +109,7 @@ const NumberFormatInputControlled = ({
|
|||
name="numberFormat"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<NumberFormatInput
|
||||
onChange={newValue => {
|
||||
onChange={(newValue?: NumberFormat) => {
|
||||
onChange(newValue);
|
||||
onSubmit();
|
||||
}}
|
||||
|
|
@ -130,9 +133,9 @@ function ChartSeriesEditorComponent({
|
|||
setValue,
|
||||
showGroupBy,
|
||||
tableName: _tableName,
|
||||
watch,
|
||||
parentRef,
|
||||
length,
|
||||
tableSource,
|
||||
}: {
|
||||
control: Control<any>;
|
||||
databaseName: string;
|
||||
|
|
@ -147,25 +150,31 @@ function ChartSeriesEditorComponent({
|
|||
setValue: UseFormSetValue<any>;
|
||||
showGroupBy: boolean;
|
||||
tableName: string;
|
||||
watch: UseFormWatch<any>;
|
||||
length: number;
|
||||
tableSource?: TSource;
|
||||
}) {
|
||||
const aggFn = watch(`${namePrefix}aggFn`);
|
||||
const aggConditionLanguage = watch(
|
||||
`${namePrefix}aggConditionLanguage`,
|
||||
'lucene',
|
||||
);
|
||||
const aggFn = useWatch({ control, name: `${namePrefix}aggFn` });
|
||||
const aggConditionLanguage = useWatch({
|
||||
control,
|
||||
name: `${namePrefix}aggConditionLanguage`,
|
||||
defaultValue: 'lucene',
|
||||
});
|
||||
|
||||
const metricType = watch(`${namePrefix}metricType`);
|
||||
const selectedSourceId = watch('source');
|
||||
const { data: tableSource } = useSource({ id: selectedSourceId });
|
||||
const metricType = useWatch({ control, name: `${namePrefix}metricType` });
|
||||
|
||||
// Initialize metricType to 'gauge' when switching to a metric source
|
||||
useEffect(() => {
|
||||
if (tableSource?.kind === SourceKind.Metric && !metricType) {
|
||||
setValue(`${namePrefix}metricType`, MetricsDataType.Gauge);
|
||||
}
|
||||
}, [tableSource?.kind, metricType, namePrefix, setValue]);
|
||||
|
||||
const tableName =
|
||||
tableSource?.kind === SourceKind.Metric
|
||||
? getMetricTableName(tableSource, metricType)
|
||||
: _tableName;
|
||||
|
||||
const metricName = watch(`${namePrefix}metricName`);
|
||||
const metricName = useWatch({ control, name: `${namePrefix}metricName` });
|
||||
const { data: attributeKeys } = useFetchMetricResourceAttrs({
|
||||
databaseName,
|
||||
tableName: tableName || '',
|
||||
|
|
@ -243,7 +252,7 @@ function ChartSeriesEditorComponent({
|
|||
control={control}
|
||||
/>
|
||||
</div>
|
||||
{tableSource?.kind === SourceKind.Metric && (
|
||||
{tableSource?.kind === SourceKind.Metric && metricType && (
|
||||
<div style={{ minWidth: 220 }}>
|
||||
<MetricNameSelect
|
||||
metricName={metricName}
|
||||
|
|
@ -257,6 +266,7 @@ function ChartSeriesEditorComponent({
|
|||
setValue(`${namePrefix}metricType`, value)
|
||||
}
|
||||
metricSource={tableSource}
|
||||
data-testid="metric-name-selector"
|
||||
/>
|
||||
{metricType === 'gauge' && (
|
||||
<Flex justify="end">
|
||||
|
|
@ -762,7 +772,7 @@ export default function EditTimeChartForm({
|
|||
fields.length === 1 && displayType !== DisplayType.Number
|
||||
}
|
||||
tableName={tableName ?? ''}
|
||||
watch={watch}
|
||||
tableSource={tableSource}
|
||||
/>
|
||||
))}
|
||||
{fields.length > 1 && displayType !== DisplayType.Number && (
|
||||
|
|
@ -1123,10 +1133,12 @@ export default function EditTimeChartForm({
|
|||
connection: tableSource.connection,
|
||||
from: tableSource.from,
|
||||
limit: { limit: 200 },
|
||||
// Search mode requires a string select, not an array of aggregations
|
||||
select:
|
||||
queriedConfig.select ||
|
||||
tableSource?.defaultTableSelectExpression ||
|
||||
'',
|
||||
typeof queriedConfig.select === 'string' &&
|
||||
queriedConfig.select
|
||||
? queriedConfig.select
|
||||
: tableSource?.defaultTableSelectExpression || '',
|
||||
groupBy: undefined,
|
||||
granularity: undefined,
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ function HistogramChart({
|
|||
);
|
||||
}, [graphResults]);
|
||||
|
||||
const barChartRef = useRef<any>();
|
||||
const activeBar = useRef<CategoricalChartState>();
|
||||
const barChartRef = useRef<any>(null);
|
||||
const activeBar = useRef<CategoricalChartState | undefined>(undefined);
|
||||
|
||||
useHotkeys(['esc'], () => {
|
||||
activeBar.current = undefined;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { useMemo } from 'react';
|
|||
import Link from 'next/link';
|
||||
import { ClickHouseQueryError } from '@hyperdx/common-utils/dist/clickhouse';
|
||||
import { ChartConfigWithDateRange } from '@hyperdx/common-utils/dist/types';
|
||||
import type { FloatingPosition } from '@mantine/core';
|
||||
import { Box, Code, Flex, HoverCard, Text } from '@mantine/core';
|
||||
import { FloatingPosition } from '@mantine/core/lib/components/Floating';
|
||||
|
||||
import { useQueriedChartConfig } from '@/hooks/useChartConfig';
|
||||
import type { NumberFormat } from '@/types';
|
||||
|
|
@ -122,7 +122,7 @@ function ListBar({
|
|||
</Text>
|
||||
<Text size="xs" span>
|
||||
{column.numberFormat != null
|
||||
? (formatNumber(value, column.numberFormat) ?? 'N/A')
|
||||
? formatNumber(value, column.numberFormat) ?? 'N/A'
|
||||
: value}
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -412,7 +412,9 @@ export function DBRowJsonViewer({
|
|||
maw="400px"
|
||||
placeholder="Search properties by key or value"
|
||||
value={filter}
|
||||
onChange={e => setFilter(e.currentTarget.value)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setFilter(e.currentTarget.value)
|
||||
}
|
||||
leftSection={<i className="bi bi-search" />}
|
||||
/>
|
||||
{filter && (
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export interface DBRowTableFieldWithPopoverProps {
|
|||
cellValue: unknown;
|
||||
wrapLinesEnabled: boolean;
|
||||
columnName?: string;
|
||||
tableContainerRef?: React.RefObject<HTMLDivElement>;
|
||||
tableContainerRef?: React.RefObject<HTMLDivElement | null>;
|
||||
isChart?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -30,8 +30,8 @@ export const DBRowTableFieldWithPopover = ({
|
|||
const [opened, { close, open }] = useDisclosure(false);
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const [hoverDisabled, setHoverDisabled] = useState(false);
|
||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||
const hoverDisableTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
const hoverDisableTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
|
||||
|
||||
// Cleanup timeouts on unmount to prevent memory leaks
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default function TableHeader({
|
|||
header: Header<any, any>;
|
||||
lastItemButtons?: React.ReactNode;
|
||||
}) {
|
||||
'use no memo'; // todo: table headers arent being resized properly with the react compiler
|
||||
return (
|
||||
<th
|
||||
className="overflow-hidden"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Meta } from '@storybook/react';
|
||||
import type { Meta } from '@storybook/nextjs';
|
||||
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
top: 0;
|
||||
height: calc(100% + 1px);
|
||||
max-height: 24px;
|
||||
border-bottom: 1px solid var(color-border);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.lineMenuBtn {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Meta } from '@storybook/react';
|
||||
import type { Meta } from '@storybook/nextjs';
|
||||
|
||||
import HyperJson from './HyperJson';
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ export function MetricNameSelect({
|
|||
isLoading,
|
||||
isError,
|
||||
metricSource,
|
||||
'data-testid': dataTestId,
|
||||
}: {
|
||||
dateRange?: DateRange['dateRange'];
|
||||
metricType: MetricsDataType;
|
||||
|
|
@ -122,6 +123,7 @@ export function MetricNameSelect({
|
|||
isLoading?: boolean;
|
||||
isError?: boolean;
|
||||
metricSource: TSource;
|
||||
'data-testid'?: string;
|
||||
}) {
|
||||
const SEPARATOR = ':::::::';
|
||||
|
||||
|
|
@ -159,6 +161,9 @@ export function MetricNameSelect({
|
|||
return metricsFromQuery;
|
||||
}, [gaugeMetrics, histogramMetrics, sumMetrics, metricName, metricType]);
|
||||
|
||||
const currentValue =
|
||||
metricName && metricType ? `${metricName}${SEPARATOR}${metricType}` : null;
|
||||
|
||||
return (
|
||||
<Select
|
||||
disabled={isLoading || isError}
|
||||
|
|
@ -177,7 +182,7 @@ export function MetricNameSelect({
|
|||
width: 'auto',
|
||||
zIndex: 1111,
|
||||
}}
|
||||
value={`${metricName}${SEPARATOR}${metricType}`}
|
||||
value={currentValue}
|
||||
searchable
|
||||
clearable
|
||||
onChange={value => {
|
||||
|
|
@ -187,6 +192,7 @@ export function MetricNameSelect({
|
|||
setMetricType(_metricType.toLowerCase() as MetricsDataType);
|
||||
}
|
||||
}}
|
||||
data-testid={dataTestId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const FORMAT_ICONS: Record<string, string> = {
|
|||
time: 'clock',
|
||||
};
|
||||
|
||||
export const NumberFormatForm: React.VFC<{
|
||||
export const NumberFormatForm: React.FC<{
|
||||
value?: NumberFormat;
|
||||
onApply: (value: NumberFormat) => void;
|
||||
onClose: () => void;
|
||||
|
|
@ -198,7 +198,7 @@ export const NumberFormatForm: React.VFC<{
|
|||
|
||||
const TEST_NUMBER = 1234;
|
||||
|
||||
export const NumberFormatInput: React.VFC<{
|
||||
export const NumberFormatInput: React.FC<{
|
||||
value?: NumberFormat;
|
||||
onChange: (value?: NumberFormat) => void;
|
||||
}> = ({ value, onChange }) => {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const SourceSelectRightSection = ({
|
|||
rightSection: (
|
||||
<>
|
||||
<UnstyledButton
|
||||
onClick={e => {
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export const Table = <T extends Record<string, unknown> | string[]>({
|
|||
);
|
||||
};
|
||||
|
||||
export const TableCellButton: React.VFC<{
|
||||
export const TableCellButton: React.FC<{
|
||||
title?: string;
|
||||
label: React.ReactNode;
|
||||
biIcon?: string;
|
||||
|
|
|
|||
|
|
@ -117,7 +117,9 @@ export const Tags = React.memo(
|
|||
m={8}
|
||||
mb={0}
|
||||
value={q}
|
||||
onChange={event => setQ(event.currentTarget.value)}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setQ(event.currentTarget.value)
|
||||
}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
rightSection={
|
||||
q && (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs';
|
||||
|
||||
import { TimePicker } from '@/components/TimePicker';
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|||
import { InputControlled, PasswordInputControlled } from '../InputControlled';
|
||||
|
||||
// Test wrapper component that provides form context
|
||||
function TestForm({ children }: { children: React.ReactNode }) {
|
||||
function TestForm({ children }: { children: React.ReactElement }) {
|
||||
const { control } = useForm({
|
||||
defaultValues: {
|
||||
testInput: '',
|
||||
|
|
@ -13,11 +13,7 @@ function TestForm({ children }: { children: React.ReactNode }) {
|
|||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form>
|
||||
{React.cloneElement(children as React.ReactElement, { control })}
|
||||
</form>
|
||||
);
|
||||
return <form>{React.cloneElement(children, { control } as any)}</form>;
|
||||
}
|
||||
|
||||
describe('InputControlled', () => {
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ export function useRRWebEventStream(
|
|||
|
||||
const lastAbortController = useRef<AbortController | null>(null);
|
||||
const [fetchStatus, setFetchStatus] = useState<'fetching' | 'idle'>('idle');
|
||||
const lastFetchStatusRef = useRef<'fetching' | 'idle' | undefined>();
|
||||
const lastFetchStatusRef = useRef<'fetching' | 'idle' | undefined>(undefined);
|
||||
|
||||
const { data: source } = useSource({ id: sourceId });
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Meta } from '@storybook/react';
|
||||
import type { StoryObj } from '@storybook/react';
|
||||
import type { Meta } from '@storybook/nextjs';
|
||||
import type { StoryObj } from '@storybook/nextjs';
|
||||
|
||||
import AppNav from '../AppNav';
|
||||
import { AppNavUserMenu } from '../AppNav.components';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Button } from '@mantine/core';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import type { Meta } from '@storybook/nextjs';
|
||||
|
||||
// Just a test story, can be deleted
|
||||
|
||||
|
|
|
|||
|
|
@ -174,7 +174,9 @@ export function useTimeQuery({
|
|||
)
|
||||
: undefined;
|
||||
|
||||
const inputTimeQueryDerivedTimeQueryRef = useRef<[Date, Date] | undefined>();
|
||||
const inputTimeQueryDerivedTimeQueryRef = useRef<[Date, Date] | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
// When the inputTimeQuery changes, we should calculate the time range
|
||||
// and set the timeRangeQuery if there is no existing time range query
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@ export const useIsTerms = () => {
|
|||
};
|
||||
|
||||
export const usePrevious = <T>(value: T): T | undefined => {
|
||||
const ref = useRef<T>();
|
||||
const ref = useRef<T | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -64,14 +64,17 @@ test.describe('Dashboard', { tag: ['@dashboard'] }, () => {
|
|||
const demoMetricsOption = page.locator('text=Demo Metrics');
|
||||
await expect(demoMetricsOption).toBeVisible();
|
||||
await demoMetricsOption.click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Wait for the metric selector to appear
|
||||
const metricSelector = page.locator(
|
||||
'input[placeholder*="metric"], input[placeholder*="Select a metric"]',
|
||||
'[data-testid="metric-name-selector"]',
|
||||
);
|
||||
await expect(metricSelector).toBeVisible();
|
||||
await expect(metricSelector).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Click to open the dropdown first
|
||||
await metricSelector.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Type the metric name to filter options
|
||||
await metricSelector.fill('k8s.container.cpu_limit');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"skipLibCheck": true,
|
||||
|
|
@ -15,31 +11,18 @@
|
|||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"jsx": "preserve",
|
||||
"types": [
|
||||
"@types/intercom-web",
|
||||
"jest"
|
||||
],
|
||||
"types": ["@types/intercom-web", "jest"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
"@styles/*": [
|
||||
"styles/*"
|
||||
]
|
||||
"@/*": ["src/*"],
|
||||
"@styles/*": ["styles/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
keys
|
||||
node_modules
|
||||
archive
|
||||
dist
|
||||
migrations
|
||||
migrate-mongo-config.ts
|
||||
tsup.config.ts
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint', 'prettier', 'simple-import-sort'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:n/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:security/recommended-legacy',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-empty-interface': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-namespace': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'n/no-process-exit': 'warn',
|
||||
'n/no-missing-import': 'off',
|
||||
'n/no-unpublished-import': [
|
||||
'error',
|
||||
{
|
||||
allowModules: ['supertest'],
|
||||
},
|
||||
],
|
||||
'n/no-unsupported-features/es-syntax': [
|
||||
'error',
|
||||
{
|
||||
ignores: ['modules'],
|
||||
},
|
||||
],
|
||||
'prettier/prettier': 'error',
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
},
|
||||
};
|
||||
94
packages/common-utils/eslint.config.mjs
Normal file
94
packages/common-utils/eslint.config.mjs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import js from '@eslint/js';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import prettierConfig from 'eslint-config-prettier';
|
||||
import prettierPlugin from 'eslint-plugin-prettier';
|
||||
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
||||
import nodePlugin from 'eslint-plugin-n';
|
||||
import securityPlugin from 'eslint-plugin-security';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
prettierConfig,
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'dist/**',
|
||||
'coverage/**',
|
||||
'**/*.config.js',
|
||||
'**/*.config.mjs',
|
||||
'jest.config.js',
|
||||
],
|
||||
},
|
||||
{
|
||||
files: ['src/**/*.ts'],
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint.plugin,
|
||||
'simple-import-sort': simpleImportSort,
|
||||
'prettier': prettierPlugin,
|
||||
'n': nodePlugin,
|
||||
'security': securityPlugin,
|
||||
},
|
||||
rules: {
|
||||
...nodePlugin.configs.recommended.rules,
|
||||
...securityPlugin.configs['recommended-legacy'].rules,
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/no-empty-interface': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-namespace': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'n/no-process-exit': 'warn',
|
||||
'n/no-missing-import': 'off',
|
||||
'n/no-unpublished-import': [
|
||||
'error',
|
||||
{
|
||||
allowModules: ['supertest'],
|
||||
},
|
||||
],
|
||||
'n/no-unsupported-features/es-syntax': [
|
||||
'error',
|
||||
{
|
||||
ignores: ['modules'],
|
||||
},
|
||||
],
|
||||
'prettier/prettier': 'error',
|
||||
'simple-import-sort/imports': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
},
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
globals: {
|
||||
console: 'readonly',
|
||||
process: 'readonly',
|
||||
module: 'readonly',
|
||||
require: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
__filename: 'readonly',
|
||||
exports: 'readonly',
|
||||
Buffer: 'readonly',
|
||||
setTimeout: 'readonly',
|
||||
clearTimeout: 'readonly',
|
||||
setInterval: 'readonly',
|
||||
clearInterval: 'readonly',
|
||||
setImmediate: 'readonly',
|
||||
clearImmediate: 'readonly',
|
||||
global: 'readonly',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -703,6 +703,7 @@ type AllKeys<T> = T extends any ? keyof T : never;
|
|||
// 3. [keyof T]
|
||||
// Indexes into the mapped type to get the union of all non-never values
|
||||
type NonOptionalKeysPresentInEveryUnionBranch<T> = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
|
||||
}[keyof T];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue