Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support feature flag overrides via query string #2945

Merged
merged 1 commit into from
Jun 26, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const updateClusterSettings = async (
notebookTolerationSettings,
modelServingPlatformEnabled,
} = request.body;
const dashConfig = getDashboardConfig();
const dashConfig = getDashboardConfig(request);
const isJupyterEnabled = checkJupyterEnabled();
try {
if (
Expand Down Expand Up @@ -124,10 +124,11 @@ export const updateClusterSettings = async (

export const getClusterSettings = async (
fastify: KubeFastifyInstance,
request: FastifyRequest,
): Promise<ClusterSettings | string> => {
const coreV1Api = fastify.kube.coreV1Api;
const namespace = fastify.kube.namespace;
const dashConfig = getDashboardConfig();
const dashConfig = getDashboardConfig(request);
const isJupyterEnabled = checkJupyterEnabled();
const clusterSettings: ClusterSettings = {
...DEFAULT_CLUSTER_SETTINGS,
Expand Down
2 changes: 1 addition & 1 deletion backend/src/routes/api/cluster-settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default async (fastify: FastifyInstance): Promise<void> => {
fastify.get(
'/',
secureAdminRoute(fastify)(async (request: FastifyRequest, reply: FastifyReply) => {
return getClusterSettings(fastify)
return getClusterSettings(fastify, request)
.then((res) => {
return res;
})
Expand Down
4 changes: 2 additions & 2 deletions backend/src/routes/api/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default async (fastify: FastifyInstance): Promise<void> => {
reply: FastifyReply,
) => {
try {
const dashConfig = getDashboardConfig();
const dashConfig = getDashboardConfig(request);
if (dashConfig?.spec.dashboardConfig.disableS3Endpoint !== false) {
reply.code(404).send('Not found');
return reply;
Expand Down Expand Up @@ -49,7 +49,7 @@ export default async (fastify: FastifyInstance): Promise<void> => {
reply: FastifyReply,
) => {
try {
const dashConfig = getDashboardConfig();
const dashConfig = getDashboardConfig(request);
if (dashConfig?.spec.dashboardConfig.disableS3Endpoint !== false) {
reply.code(404).send('Not found');
return reply;
Expand Down
29 changes: 27 additions & 2 deletions backend/src/utils/resourceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { getIsAppEnabled, getRouteForApplication, getRouteForClusterId } from '.
import { createCustomError } from './requestUtils';
import { getDetectedAccelerators } from '../routes/api/accelerators/acceleratorUtils';
import { RecursivePartial } from '../typeHelpers';
import { FastifyRequest } from 'fastify';

const dashboardConfigMapName = 'odh-dashboard-config';
const consoleLinksGroup = 'console.openshift.io';
Expand Down Expand Up @@ -548,8 +549,32 @@ export const initializeWatchedResources = (fastify: KubeFastifyInstance): void =
consoleLinksWatcher = new ResourceWatcher<ConsoleLinkKind>(fastify, fetchConsoleLinks);
};

export const getDashboardConfig = (): DashboardConfig => {
return dashboardConfigWatcher.getResources()?.[0];
const FEATURE_FLAGS_HEADER = 'x-odh-feature-flags';

// if inspecting feature flags, provide the request to ensure overridden feature flags are considered
export const getDashboardConfig = (request?: FastifyRequest): DashboardConfig => {
const dashboardConfig = dashboardConfigWatcher.getResources()?.[0];
if (request) {
const flagsHeader = request.headers[FEATURE_FLAGS_HEADER];
if (typeof flagsHeader === 'string') {
try {
const featureFlags = JSON.parse(flagsHeader);
return {
...dashboardConfig,
spec: {
...dashboardConfig.spec,
dashboardConfig: {
...dashboardConfig.spec.dashboardConfig,
...featureFlags,
},
},
};
} catch {
// ignore
}
}
}
return dashboardConfig;
};

export const updateDashboardConfig = (): Promise<void> => {
Expand Down
18 changes: 14 additions & 4 deletions frontend/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,25 @@
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "^axios$",
"importNames": ["default"],
"message": "Import from `~/utilities/axios` instead."
}
],
"patterns": [
{
"group": ["~/api/**"],
"message": "Read from '~/api' instead."
"message": "Import from '~/api' instead."
},
{
"group": ["~/components/table/**", "!~/components/table/useTableColumnSort"],
"message": "Read from '~/components/table' instead."
"message": "Import from '~/components/table' instead."
},
{
"group": ["~/concepts/area/**"],
"message": "Read from '~/concepts/area' instead."
"message": "Import from '~/concepts/area' instead."
},
{
"group": ["~/components/table/useTableColumnSort"],
Expand Down Expand Up @@ -286,7 +293,10 @@
],
"overrides": [
{
"files": ["./src/__tests__/cypress/cypress/pages/*.ts", "./src/__tests__/cypress/cypress/tests/e2e/*.ts"],
"files": [
"./src/__tests__/cypress/cypress/pages/*.ts",
"./src/__tests__/cypress/cypress/tests/e2e/*.ts"
],
"rules": {
"no-restricted-syntax": [
"error",
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/api/k8s/__tests__/projects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
k8sUpdateResource,
k8sDeleteResource,
} from '@openshift/dynamic-plugin-sdk-utils';
import axios from 'axios';
import axios from '~/utilities/axios';
import { mockProjectK8sResource } from '~/__mocks__/mockProjectK8sResource';
import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList';
import { mockAxiosError } from '~/__mocks__/mockAxiosError';
Expand Down Expand Up @@ -40,7 +40,7 @@ jest.mock('~/api/k8s/servingRuntimes.ts', () => ({
listServingRuntimes: jest.fn(),
}));

jest.mock('axios');
jest.mock('~/utilities/axios');

const mockedAxios = jest.mocked(axios);
const k8sListResourceMock = jest.mocked(k8sListResource<ProjectKind>);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/k8s/projects.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import axios from 'axios';
import {
k8sCreateResource,
k8sDeleteResource,
k8sListResource,
K8sResourceCommon,
k8sUpdateResource,
} from '@openshift/dynamic-plugin-sdk-utils';
import axios from '~/utilities/axios';
import { CustomWatchK8sResult } from '~/types';
import { K8sAPIOptions, ProjectKind } from '~/k8sTypes';
import { ProjectModel, ProjectRequestModel } from '~/api/models';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { act } from '@testing-library/react';
import axios from 'axios';
import axios from '~/utilities/axios';
import { mockPrometheusQueryVectorResponse } from '~/__mocks__/mockPrometheusQueryVectorResponse';
import { mockWorkloadK8sResource } from '~/__mocks__/mockWorkloadK8sResource';
import { WorkloadKind, WorkloadOwnerType } from '~/k8sTypes';
Expand Down Expand Up @@ -310,7 +310,7 @@ describe('getTopResourceConsumingWorkloads', () => {
});
});

jest.mock('axios', () => ({
jest.mock('~/utilities/axios', () => ({
post: jest.fn(),
}));

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/api/prometheus/__tests__/pvcs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { act } from '@testing-library/react';
import axios from 'axios';
import axios from '~/utilities/axios';
import { mockPVCK8sResource } from '~/__mocks__/mockPVCK8sResource';
import { mockPrometheusQueryResponse } from '~/__mocks__/mockPrometheusQueryResponse';
import { testHook } from '~/__tests__/unit/testUtils/hooks';
import { usePVCFreeAmount } from '~/api/prometheus/pvcs';
import { POLL_INTERVAL } from '~/utilities/const';

jest.mock('axios', () => ({
jest.mock('~/utilities/axios', () => ({
post: jest.fn(),
}));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { act } from '@testing-library/react';
import axios from 'axios';
import axios from '~/utilities/axios';
import { mockPrometheusQueryResponse } from '~/__mocks__/mockPrometheusQueryResponse';
import { standardUseFetchState, testHook } from '~/__tests__/unit/testUtils/hooks';
import usePrometheusQuery from '~/api/prometheus/usePrometheusQuery';

jest.mock('axios', () => ({
jest.mock('~/utilities/axios', () => ({
post: jest.fn(),
}));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import axios from 'axios';
import { act } from '@testing-library/react';
import axios from '~/utilities/axios';
import { testHook } from '~/__tests__/unit/testUtils/hooks';
import { mockPrometheusServing } from '~/__mocks__/mockPrometheusServing';
import usePrometheusQueryRange from '~/api/prometheus/usePrometheusQueryRange';
import { PrometheusQueryRangeResponseData } from '~/types';

jest.mock('axios', () => ({
jest.mock('~/utilities/axios', () => ({
post: jest.fn(),
}));

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/prometheus/usePrometheusQuery.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import axios from 'axios';
import axios from '~/utilities/axios';
import { PrometheusQueryResponse } from '~/types';
import useFetchState, { FetchOptions, FetchState, NotReadyError } from '~/utilities/useFetchState';

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/prometheus/usePrometheusQueryRange.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import axios from 'axios';
import axios from '~/utilities/axios';

import useFetchState, {
FetchOptions,
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import useDetectUser from '~/utilities/useDetectUser';
import ProjectsContextProvider from '~/concepts/projects/ProjectsContext';
import useStorageClasses from '~/concepts/k8s/useStorageClasses';
import AreaContextProvider from '~/concepts/areas/AreaContext';
import useDevFeatureFlags from './useDevFeatureFlags';
import Header from './Header';
import AppRoutes from './AppRoutes';
import NavSidebar from './NavSidebar';
Expand All @@ -29,6 +30,7 @@ import { useApplicationSettings } from './useApplicationSettings';
import TelemetrySetup from './TelemetrySetup';
import { logout } from './appUtils';
import QuickStarts from './QuickStarts';
import DevFeatureFlagsBanner from './DevFeatureFlagsBanner';

import './App.scss';

Expand All @@ -38,11 +40,14 @@ const App: React.FC = () => {

const buildStatuses = useWatchBuildStatus();
const {
dashboardConfig,
dashboardConfig: dashboardConfigFromServer,
loaded: configLoaded,
loadError: fetchConfigError,
} = useApplicationSettings();

const { dashboardConfig, ...devFeatureFlagsProps } =
useDevFeatureFlags(dashboardConfigFromServer);

const [storageClasses] = useStorageClasses();

useDetectUser();
Expand Down Expand Up @@ -115,6 +120,10 @@ const App: React.FC = () => {
data-testid={DASHBOARD_MAIN_CONTAINER_ID}
>
<ErrorBoundary>
<DevFeatureFlagsBanner
dashboardConfig={dashboardConfig.spec.dashboardConfig}
{...devFeatureFlagsProps}
/>
<ProjectsContextProvider>
<QuickStarts>
<AppRoutes />
Expand Down
Loading
Loading