mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
## Summary Introduces a "Shared Filters" feature (in addition to locally pinned items in HyperDX. Especially helpful for teams with lots of filters and team members, allows users to highlight the top filters easily for all members. This has been one of the most requested features we have received from enterprise customers. \ > **Note:** - currently any user on a team can modify shared filters - we may want/need to introduce role limits to this, but that is oos ### Screenshots or video https://github.com/user-attachments/assets/9613d37c-d8d6-4aeb-9e47-1ad25532a862 ### How to test locally or on Vercel 1. Start the dev server (`yarn dev`) 2. Navigate to the Search page 3. Pin a filter field using the 📌 icon on any filter group header —you should be asked to pin (existing) or add to shared filters. 4. Share a specific value by hovering over a filter checkbox row and clicking the pin icon — it should also appear in Shared Filters 5. Reload the page — pins should persist (MongoDB-backed) 6. Open a second browser/incognito window with the same team — pins should be visible there too 7. Click the ⚙ gear icon next to "Filters" — toggle "Show Shared Filters" off/on 8. Click "Reset Shared Filters" in the gear popover to clear all team pins ### References - Linear Issue: https://linear.app/clickhouse/issue/HDX-2300/sailpoint-neara-global-filter-pinning - Related PRs: Previous WIP branch `brandon/shared-filters-ui` (superseded by this implementation)
135 lines
4.9 KiB
TypeScript
135 lines
4.9 KiB
TypeScript
import compression from 'compression';
|
|
import MongoStore from 'connect-mongo';
|
|
import express from 'express';
|
|
import session from 'express-session';
|
|
import onHeaders from 'on-headers';
|
|
|
|
import * as config from './config';
|
|
import mcpRouter from './mcp/app';
|
|
import { isUserAuthenticated } from './middleware/auth';
|
|
import defaultCors from './middleware/cors';
|
|
import { appErrorHandler } from './middleware/error';
|
|
import routers from './routers/api';
|
|
import clickhouseProxyRouter from './routers/api/clickhouseProxy';
|
|
import connectionsRouter from './routers/api/connections';
|
|
import favoritesRouter from './routers/api/favorites';
|
|
import pinnedFiltersRouter from './routers/api/pinnedFilters';
|
|
import savedSearchRouter from './routers/api/savedSearch';
|
|
import sourcesRouter from './routers/api/sources';
|
|
import externalRoutersV2 from './routers/external-api/v2';
|
|
import usageStats from './tasks/usageStats';
|
|
import logger, { expressLogger } from './utils/logger';
|
|
import passport from './utils/passport';
|
|
|
|
const app: express.Application = express();
|
|
|
|
const sess: session.SessionOptions & { cookie: session.CookieOptions } = {
|
|
// Use a slot-specific cookie name in dev so multiple worktrees on localhost
|
|
// don't overwrite each other's session cookies.
|
|
...(config.IS_DEV && process.env.HDX_DEV_SLOT
|
|
? { name: `connect.sid.${process.env.HDX_DEV_SLOT}` }
|
|
: {}),
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
secret: config.EXPRESS_SESSION_SECRET,
|
|
cookie: {
|
|
secure: false,
|
|
sameSite: 'lax',
|
|
maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days
|
|
},
|
|
rolling: true,
|
|
store: new MongoStore({ mongoUrl: config.MONGO_URI }),
|
|
};
|
|
|
|
app.set('trust proxy', 1);
|
|
if (!config.IS_CI && config.FRONTEND_URL) {
|
|
const feUrl = new URL(config.FRONTEND_URL);
|
|
sess.cookie.domain = feUrl.hostname;
|
|
if (feUrl.protocol === 'https:') {
|
|
sess.cookie.secure = true;
|
|
}
|
|
}
|
|
|
|
app.disable('x-powered-by');
|
|
app.use(compression());
|
|
app.use(express.json({ limit: '32mb' }));
|
|
app.use(express.text({ limit: '32mb' }));
|
|
app.use(express.urlencoded({ extended: false, limit: '32mb' }));
|
|
app.use(session(sess));
|
|
|
|
if (!config.IS_LOCAL_APP_MODE) {
|
|
app.use(passport.initialize());
|
|
app.use(passport.session());
|
|
}
|
|
|
|
if (!config.IS_CI) {
|
|
app.use(expressLogger);
|
|
}
|
|
// Allows timing data from frontend package
|
|
// see: https://github.com/expressjs/cors/issues/102
|
|
app.use(function (req, res, next) {
|
|
onHeaders(res, function () {
|
|
const allowOrigin = res.getHeader('Access-Control-Allow-Origin');
|
|
if (allowOrigin) {
|
|
res.setHeader('Timing-Allow-Origin', allowOrigin);
|
|
}
|
|
});
|
|
next();
|
|
});
|
|
app.use(defaultCors);
|
|
|
|
// ---------------------------------------------------------------------
|
|
// ----------------------- Background Jobs -----------------------------
|
|
// ---------------------------------------------------------------------
|
|
if (config.USAGE_STATS_ENABLED && !config.IS_CI) {
|
|
usageStats();
|
|
}
|
|
// ---------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------
|
|
// ----------------------- Internal Routers ----------------------------
|
|
// ---------------------------------------------------------------------
|
|
// PUBLIC ROUTES
|
|
app.use('/', routers.rootRouter);
|
|
|
|
// SELF-AUTHENTICATED ROUTES (validated via access key, not session middleware)
|
|
app.use('/mcp', mcpRouter);
|
|
|
|
// PRIVATE ROUTES
|
|
app.use('/ai', isUserAuthenticated, routers.aiRouter);
|
|
app.use('/alerts', isUserAuthenticated, routers.alertsRouter);
|
|
app.use('/dashboards', isUserAuthenticated, routers.dashboardRouter);
|
|
app.use('/me', isUserAuthenticated, routers.meRouter);
|
|
app.use('/team', isUserAuthenticated, routers.teamRouter);
|
|
app.use('/webhooks', isUserAuthenticated, routers.webhooksRouter);
|
|
app.use('/connections', isUserAuthenticated, connectionsRouter);
|
|
app.use('/sources', isUserAuthenticated, sourcesRouter);
|
|
app.use('/saved-search', isUserAuthenticated, savedSearchRouter);
|
|
app.use('/favorites', isUserAuthenticated, favoritesRouter);
|
|
app.use('/pinned-filters', isUserAuthenticated, pinnedFiltersRouter);
|
|
app.use('/clickhouse-proxy', isUserAuthenticated, clickhouseProxyRouter);
|
|
// ---------------------------------------------------------------------
|
|
|
|
// TODO: Separate external API routers from internal routers
|
|
// ---------------------------------------------------------------------
|
|
// ----------------------- External Routers ----------------------------
|
|
// ---------------------------------------------------------------------
|
|
// API v2
|
|
// Only initialize Swagger in development or if explicitly enabled
|
|
if (
|
|
process.env.NODE_ENV !== 'production' &&
|
|
process.env.ENABLE_SWAGGER === 'true'
|
|
) {
|
|
// Will require a refactor to ESM to use import statements
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
const { setupSwagger } = require('./utils/swagger');
|
|
setupSwagger(app);
|
|
logger.info('Swagger UI setup and available at /api/v2/docs');
|
|
}
|
|
|
|
app.use('/api/v2', externalRoutersV2);
|
|
|
|
// error handling
|
|
app.use(appErrorHandler);
|
|
|
|
export default app;
|