Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ OPS_TABLES_TOKEN_LIFETIME_MINUTES=60
OPS_MAX_CONCURRENT_TABLES_REQUESTS=100

# OPENOPS ANALYTICS
OPS_ANALYTICS_ENABLED=true
OPS_ANALYTICS_PUBLIC_URL=http://localhost:8088
OPS_ANALYTICS_PRIVATE_URL=http://localhost:8088
OPS_ANALYTICS_ADMIN_PASSWORD=admin
Expand Down
1 change: 1 addition & 0 deletions deploy/docker-compose/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ OPS_MAX_CONCURRENT_TABLES_REQUESTS=100
# Analytics
# ---------------------------------------------------------

OPS_ANALYTICS_ENABLED=true
OPS_ANALYTICS_PUBLIC_URL=${OPS_PUBLIC_URL}
OPS_ANALYTICS_PRIVATE_URL=http://openops-analytics:8088
OPS_ANALYTICS_ADMIN_PASSWORD=please-change-this-password-1
Expand Down
1 change: 1 addition & 0 deletions deploy/helm/openops/values.overrides-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ openopsEnv:
OPS_JWT_SECRET: M72yCOp9riLjIDws
OPS_POSTGRES_PASSWORD: WZKUM0tSvFC6VWai
OPS_ANALYTICS_ADMIN_PASSWORD: 7YO7YEVRaECPQimI
OPS_ANALYTICS_ENABLED: "true"
ANALYTICS_POWERUSER_PASSWORD: v5vIvwhdBDWwx4el
1 change: 1 addition & 0 deletions deploy/helm/openops/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ openopsEnv:
OPS_OPENOPS_TABLES_DB_HOST: '{{ include "openops.postgresHost" . }}'

# Analytics
OPS_ANALYTICS_ENABLED: "true"
OPS_ANALYTICS_PUBLIC_URL: "{{ .Values.openopsEnv.OPS_PUBLIC_URL }}"
OPS_ANALYTICS_PRIVATE_URL: '{{ include "openops.analyticsServiceUrl" . }}'
OPS_ANALYTICS_ADMIN_PASSWORD: please-change-this-password-1
Expand Down
18 changes: 18 additions & 0 deletions packages/react-ui/src/app/common/hooks/analytics-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { authenticationSession } from '@/app/lib/authentication-session';
import { FlagId } from '@openops/shared';
import { useMemo } from 'react';
import { flagsHooks } from './flags-hooks';

export const useHasAnalyticsAccess = (): boolean => {
const { data: isAnalyticsEnabled } = flagsHooks.useFlag<boolean | undefined>(
FlagId.ANALYTICS_ENABLED,
);

const hasAnalyticsPrivileges =
authenticationSession.getUserHasAnalyticsPrivileges();

return useMemo(
() => Boolean(isAnalyticsEnabled && hasAnalyticsPrivileges),
[isAnalyticsEnabled, hasAnalyticsPrivileges],
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { authenticationSession } from '@/app/lib/authentication-session';
import { useHasAnalyticsAccess } from '@/app/common/hooks/analytics-hooks';
import { MenuLink, RunsIcon } from '@openops/components/ui';
import { t } from 'i18next';
import {
Expand All @@ -18,11 +18,10 @@ import { useMemo } from 'react';
*
*/
export const useMenuLinks = () => {
const hasAnalyticsPrivileges =
authenticationSession.getUserHasAnalyticsPrivileges();
const hasAnalyticsAccess = useHasAnalyticsAccess();

const menuLinks: MenuLink[] = useMemo(
() => [
const menuLinks: MenuLink[] = useMemo(() => {
const links: MenuLink[] = [
{
to: '/',
label: t('Overview'),
Expand All @@ -48,7 +47,7 @@ export const useMenuLinks = () => {
label: t('Tables'),
icon: TableProperties,
},
...(hasAnalyticsPrivileges
...(hasAnalyticsAccess
? [
{
to: '/analytics',
Expand All @@ -57,9 +56,10 @@ export const useMenuLinks = () => {
},
]
: []),
],
[hasAnalyticsPrivileges],
);
];

return links;
}, [hasAnalyticsAccess]);

return menuLinks;
};
52 changes: 34 additions & 18 deletions packages/react-ui/src/app/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { FlowsPage } from '../app/routes/flows';
import { PageHeader } from '@openops/components/ui';
import { t } from 'i18next';

import { useHasAnalyticsAccess } from '@/app/common/hooks/analytics-hooks';
import { flagsHooks } from '@/app/common/hooks/flags-hooks';
import { FlowsPageHeader } from '@/app/features/flows/components/flows-page-header';
import { HomeHelpDropdown } from '@/app/features/home/components/home-help-dropdown';
Expand All @@ -31,7 +32,6 @@ import { ConnectionsHeader } from './features/connections/components/connection-
import { ConnectionsProvider } from './features/connections/components/connections-context';
import { GlobalLayout } from './features/navigation/layout/global-layout';
import { RouteWrapper } from './features/navigation/layout/route-wrapper';
import { authenticationSession } from './lib/authentication-session';
import NotFoundPage from './routes/404-page';
import { ChangePasswordPage } from './routes/change-password';
import AppConnectionsPage from './routes/connections';
Expand All @@ -57,22 +57,19 @@ const SettingsRerouter = () => {
);
};

const createRoutes = () => {
const { data: isCloudConnectionPageEnabled } = flagsHooks.useFlag<any>(
FlagId.CLOUD_CONNECTION_PAGE_ENABLED,
);

const { data: isDemoHomePage } = flagsHooks.useFlag<any>(
FlagId.SHOW_DEMO_HOME_PAGE,
);

const { data: isFederatedLogin } = flagsHooks.useFlag<boolean | undefined>(
FlagId.FEDERATED_LOGIN_ENABLED,
);

const hasAnalyticsPrivileges =
authenticationSession.getUserHasAnalyticsPrivileges();
interface CreateRoutesParams {
isCloudConnectionPageEnabled: any;
isDemoHomePage: any;
isFederatedLogin: boolean | null | undefined;
hasAnalyticsAccess: boolean;
}

const createRoutes = ({
isCloudConnectionPageEnabled,
isDemoHomePage,
isFederatedLogin,
hasAnalyticsAccess,
}: CreateRoutesParams) => {
const routes = [
{
path: 'flows',
Expand Down Expand Up @@ -207,7 +204,7 @@ const createRoutes = () => {
),
errorElement: <RouteErrorBoundary />,
},
...(hasAnalyticsPrivileges
...(hasAnalyticsAccess
? [
{
path: 'analytics',
Expand Down Expand Up @@ -411,11 +408,30 @@ const createRoutes = () => {
};

const ApplicationRouter = () => {
const { data: isCloudConnectionPageEnabled } = flagsHooks.useFlag<any>(
FlagId.CLOUD_CONNECTION_PAGE_ENABLED,
);

const { data: isDemoHomePage } = flagsHooks.useFlag<any>(
FlagId.SHOW_DEMO_HOME_PAGE,
);

const { data: isFederatedLogin } = flagsHooks.useFlag<boolean | undefined>(
FlagId.FEDERATED_LOGIN_ENABLED,
);

const hasAnalyticsAccess = useHasAnalyticsAccess();

const router = createBrowserRouter([
{
path: '/',
element: <GlobalLayout />,
children: createRoutes(),
children: createRoutes({
isCloudConnectionPageEnabled,
isDemoHomePage,
isFederatedLogin,
hasAnalyticsAccess,
}),
},
]);
return <RouterProvider router={router}></RouterProvider>;
Expand Down
1 change: 1 addition & 0 deletions packages/server/api/.env.tests
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ OPS_ENGINE_URL=http://localhost:3005/execute
OPS_OPENOPS_ADMIN_EMAIL=local-admin@openops.com
OPS_OPENOPS_ADMIN_PASSWORD=12345678
OPS_OPENOPS_TABLES_PUBLIC_URL=http://localhost:3001
OPS_ANALYTICS_ENABLED=true
7 changes: 7 additions & 0 deletions packages/server/api/src/app/ai/mcp/superset-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import path from 'path';
import { MCPTool } from './types';

export async function getSupersetTools(): Promise<MCPTool> {
if (!system.isAnalyticsEnabled()) {
return {
client: undefined,
toolSet: {},
};
}

const basePath = system.get<string>(AppSystemProp.SUPERSET_MCP_SERVER_PATH);

if (!basePath) {
Expand Down
3 changes: 2 additions & 1 deletion packages/server/api/src/app/ai/mcp/tools-initializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ export const startMCPTools = async (
const loadExperimentalTools = system.getBoolean(
AppSystemProp.LOAD_EXPERIMENTAL_MCP_TOOLS,
);
const analyticsEnabled = system.isAnalyticsEnabled();

let supersetTools: Partial<MCPTool> = {
client: undefined,
toolSet: {},
};

if (loadExperimentalTools) {
if (loadExperimentalTools && analyticsEnabled) {
supersetTools = await safeGetTools('superset', getSupersetTools);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const AnalyticsGuestTokenRequestOptions = {
export const authenticationController: FastifyPluginAsyncTypebox = async (
app,
) => {
const analyticsEnabled = system.isAnalyticsEnabled();

app.post('/sign-up', SignUpRequestOptions, signUpRoute);
app.post('/sign-in', SignInRequestOptions, signInRoute);

Expand All @@ -60,37 +62,40 @@ export const authenticationController: FastifyPluginAsyncTypebox = async (
},
);

app.get('/analytics-embed-id', async (request, reply) => {
const { access_token } =
await analyticsAuthenticationService.authenticateAnalyticsRequest(
request.principal.id,
);

const embedId = await analyticsDashboardService.fetchFinopsDashboardEmbedId(
access_token,
);

return reply.send(embedId);
});

app.get(
'/analytics-guest-token',
AnalyticsGuestTokenRequestOptions,
async (request, reply) => {
if (analyticsEnabled) {
app.get('/analytics-embed-id', async (request, reply) => {
const { access_token } =
await analyticsAuthenticationService.authenticateAnalyticsRequest(
request.principal.id,
);

const guestToken =
await analyticsDashboardService.fetchDashboardGuestToken(
const embedId =
await analyticsDashboardService.fetchFinopsDashboardEmbedId(
access_token,
request.query.dashboardEmbedUuid,
);

return reply.send(guestToken);
},
);
return reply.send(embedId);
});

app.get(
'/analytics-guest-token',
AnalyticsGuestTokenRequestOptions,
async (request, reply) => {
const { access_token } =
await analyticsAuthenticationService.authenticateAnalyticsRequest(
request.principal.id,
);

const guestToken =
await analyticsDashboardService.fetchDashboardGuestToken(
access_token,
request.query.dashboardEmbedUuid,
);

return reply.send(guestToken);
},
);
}
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ const setAnalyticsDataSeededFlag = async (): Promise<void> => {
};

export const seedAnalytics = async (): Promise<void> => {
if (!system.isAnalyticsEnabled()) {
logger.info('Skipping analytics seeding, analytics disabled');
return;
}

if (!system.get(AppSystemProp.ANALYTICS_PRIVATE_URL)) {
logger.info('Skipping analytics seeding, no configured private URL');
return;
Expand Down
10 changes: 9 additions & 1 deletion packages/server/api/src/app/flags/flag.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,15 @@ export const flagService = {
},
{
id: FlagId.ANALYTICS_PUBLIC_URL,
value: system.get(AppSystemProp.ANALYTICS_PUBLIC_URL),
value: system.isAnalyticsEnabled()
? system.get(AppSystemProp.ANALYTICS_PUBLIC_URL)
: undefined,
Comment on lines +232 to +234
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional logic returns undefined when analytics is disabled, but system.get() already returns undefined when the value is not set. Consider simplifying to return system.get(AppSystemProp.ANALYTICS_PUBLIC_URL) ?? undefined when analytics is enabled, or verify if there's a scenario where the URL could be set but analytics disabled.

Copilot uses AI. Check for mistakes.
created,
updated,
},
{
id: FlagId.ANALYTICS_ENABLED,
value: system.isAnalyticsEnabled(),
created,
updated,
},
Expand Down
7 changes: 6 additions & 1 deletion packages/server/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,12 @@ const main = async (): Promise<void> => {
await seedFocusDataAggregationTemplateTable();
await seedKnownCostTypesByApplicationTable();
await seedAutoInstancesShutdownTable();
await analytics.seedAnalytics();

if (system.isAnalyticsEnabled()) {
await analytics.seedAnalytics();
} else {
logger.info('Skipping analytics seed run, analytics disabled');
}
}

const environmentId = await seedEnvironmentId();
Expand Down
Loading
Loading