Skip to content

Commit

Permalink
[cueweb] Make CueWeb authentication and Sentry usage optional
Browse files Browse the repository at this point in the history
- Added functionality to make login and Sentry usage optional in CueWeb.
- Updated configuration to enable/disable these features via environment variables.
- Enhanced the cueweb/README.md with instructions for configuring these options in the CueWeb setup.

Co-authored-by: Zachary Fong <[email protected]>
Co-authored-by: Ramon Figueiredo <[email protected]>
  • Loading branch information
Zach-Fong and ramonfigueiredo committed Nov 23, 2024
1 parent 8e534f2 commit 9c326b6
Show file tree
Hide file tree
Showing 21 changed files with 199 additions and 138 deletions.
7 changes: 5 additions & 2 deletions cueweb/.env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
NEXT_PUBLIC_OPENCUE_ENDPOINT=http://your-rest-gateway-url.com

# Sentry values
SENTRY_ENVIRONMENT='development'

SENTRY_DSN = sentrydsn
SENTRY_URL = sentryurl
SENTRY_ORG = sentryorg
SENTRY_PROJECT = sentryproject

# Authentication Configuration:

NEXT_PUBLIC_AUTH_PROVIDER=github,okta,google
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=canbeanything
Expand Down
37 changes: 35 additions & 2 deletions cueweb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,17 @@ Next is the process to install and use the CueWeb system.
- GitHub
- GITHUB_ID
- GITHUB_SECRET
- To disable authentication, do not define `NEXT_PUBLIC_AUTH_PROVIDER`.
Change `.env` from:
```env
# Authentication Configuration:
NEXT_PUBLIC_AUTH_PROVIDER=github,okta,google
```
to
```env
# Authentication Configuration:
# NEXT_PUBLIC_AUTH_PROVIDER=github,okta,google
```
- Sentry environment variables
- If you use [Sentry](https://sentry.io/) system to monitor your application, the following environment variables should be set:
Expand All @@ -127,17 +138,39 @@ Next is the process to install and use the CueWeb system.
- SENTRY_DSN
- SENTRY_ORG
- SENTRY_PROJECT
- If you do not want to use Sentry, do not define `SENTRY_DSN`.
Change `.env` from:
```env
# Sentry values
SENTRY_ENVIRONMENT='development'
SENTRY_DSN = sentrydsn
SENTRY_URL = sentryurl
SENTRY_ORG = sentryorg
SENTRY_PROJECT = sentryproject
```
to
```env
# Sentry values
SENTRY_ENVIRONMENT='development'
# SENTRY_DSN = sentrydsn
SENTRY_URL = sentryurl
SENTRY_ORG = sentryorg
SENTRY_PROJECT = sentryproject
```
Example of `.env` file (`cueweb/.env.example`):
```env
NEXT_PUBLIC_OPENCUE_ENDPOINT=http://your-rest-gateway-url.com
# Sentry values
SENTRY_ENVIRONMENT='development'
SENTRY_DSN = sentrydsn
SENTRY_URL = sentryurl
SENTRY_ORG = sentryorg
SENTRY_PROJECT = sentryproject
# Authentication Configuration:
NEXT_PUBLIC_AUTH_PROVIDER=github,okta,google
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=canbeanything
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,12 @@ describe('fetchObjectFromRestGateway', () => {
// Clear mocks before running each test and mock the return values of loadClientEnvVars and createJwtToken
beforeEach(() => {
jest.clearAllMocks();
(loadClientEnvVars as jest.Mock).mockReturnValue({ NEXT_PUBLIC_OPENCUE_ENDPOINT: 'http://localhost:3000' });
(loadServerEnvVars as jest.Mock).mockReturnValue({ NEXT_JWT_SECRET: 'NEXT_JWT_SECRET' });
(jwt.sign as jest.Mock).mockReturnValue('mockJwtToken');
(createJwtToken as jest.Mock).mockReturnValue('mockJwtToken');
});

/*
Given a non error response from the gRPC REST gateway expect:
- loadClientEnvVars to be called
- createJwtToken to be called with correct parameters
- status 200 and correct data to be returned
*/
Expand All @@ -50,8 +47,7 @@ describe('fetchObjectFromRestGateway', () => {
const body = JSON.stringify({ key: 'value' });
const response = await fetchObjectFromRestGateway(endpoint, method, body);

expect(loadClientEnvVars).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith('http://localhost:3000/test-endpoint', {
expect(fetch).toHaveBeenCalledWith('NEXT_PUBLIC_OPENCUE_ENDPOINT/test-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -65,7 +61,6 @@ describe('fetchObjectFromRestGateway', () => {

/*
Given a 401 status response from the gRPC REST gateway expect:
- loadClientEnvVars to be called
- createJwtToken to be called with correct parameters
- status 401 and error message 'Unauthorized request: Unauthorized error' to be returned
*/
Expand All @@ -82,8 +77,7 @@ describe('fetchObjectFromRestGateway', () => {

const response = await fetchObjectFromRestGateway(endpoint, method, body);

expect(loadClientEnvVars).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith('http://localhost:3000/test-endpoint', {
expect(fetch).toHaveBeenCalledWith('NEXT_PUBLIC_OPENCUE_ENDPOINT/test-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -97,7 +91,6 @@ describe('fetchObjectFromRestGateway', () => {

/*
Given a 404 status response from the gRPC REST gateway expect:
- loadClientEnvVars to be called
- createJwtToken to be called with correct parameters
- status 404 and error message 'Resource not found: Resource not found' to be returned
*/
Expand All @@ -114,8 +107,7 @@ describe('fetchObjectFromRestGateway', () => {

const response = await fetchObjectFromRestGateway(endpoint, method, body);

expect(loadClientEnvVars).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith('http://localhost:3000/test-endpoint', {
expect(fetch).toHaveBeenCalledWith('NEXT_PUBLIC_OPENCUE_ENDPOINT/test-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -129,7 +121,6 @@ describe('fetchObjectFromRestGateway', () => {

/*
Given a 500 status response from the gRPC REST gateway expect:
- loadClientEnvVars to be called
- createJwtToken to be called with correct parameters
- status 500 and error message 'Unexpected API Error: Unexpected error' to be returned
*/
Expand All @@ -146,8 +137,7 @@ describe('fetchObjectFromRestGateway', () => {

const response = await fetchObjectFromRestGateway(endpoint, method, body);

expect(loadClientEnvVars).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith('http://localhost:3000/test-endpoint', {
expect(fetch).toHaveBeenCalledWith('NEXT_PUBLIC_OPENCUE_ENDPOINT/test-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -161,7 +151,6 @@ describe('fetchObjectFromRestGateway', () => {

/*
Given an error when fetching from the gRPC REST gateway expect:
- loadClientEnvVars to be called
- createJwtToken to be called with correct parameters
- status 400 and error message 'Fetch error' to be returned
*/
Expand All @@ -174,8 +163,7 @@ describe('fetchObjectFromRestGateway', () => {

const response = await fetchObjectFromRestGateway(endpoint, method, body);

expect(loadClientEnvVars).toHaveBeenCalled();
expect(fetch).toHaveBeenCalledWith('http://localhost:3000/test-endpoint', {
expect(fetch).toHaveBeenCalledWith('NEXT_PUBLIC_OPENCUE_ENDPOINT/test-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand Down
32 changes: 16 additions & 16 deletions cueweb/app/__tests__/api/utils/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ describe('loadServerEnvVars', () => {
// All environment variables are already defined before the test.
// Their values are then loaded and checked against their expected values.
it('should load and verify environment variables', () => {
const envVars = loadServerEnvVars();
loadServerEnvVars();

expect(envVars.NEXTAUTH_URL).toBe('NEXTAUTH_URL')
expect(envVars.NEXTAUTH_SECRET).toBe('NEXTAUTH_SECRET')
expect(envVars.NEXT_JWT_SECRET).toBe('NEXT_JWT_SECRET')
expect(envVars.NEXT_AUTH_OKTA_CLIENT_ID).toBe('NEXT_AUTH_OKTA_CLIENT_ID')
expect(envVars.NEXT_AUTH_OKTA_ISSUER).toBe('NEXT_AUTH_OKTA_ISSUER')
expect(envVars.NEXT_AUTH_OKTA_CLIENT_SECRET).toBe('NEXT_AUTH_OKTA_CLIENT_SECRET')
expect(envVars.SENTRY_ENVIRONMENT).toBe('SENTRY_ENVIRONMENT')
expect(envVars.SENTRY_PROJECT).toBe('SENTRY_PROJECT')
expect(envVars.SENTRY_URL).toBe('SENTRY_URL')
expect(envVars.SENTRY_ORG).toBe('SENTRY_ORG')
expect(envVars.SENTRY_DSN).toBe('SENTRY_DSN')
expect(process.env.NEXTAUTH_URL).toBe('NEXTAUTH_URL')
expect(process.env.NEXTAUTH_SECRET).toBe('NEXTAUTH_SECRET')
expect(process.env.NEXT_JWT_SECRET).toBe('NEXT_JWT_SECRET')
expect(process.env.NEXT_AUTH_OKTA_CLIENT_ID).toBe('NEXT_AUTH_OKTA_CLIENT_ID')
expect(process.env.NEXT_AUTH_OKTA_ISSUER).toBe('NEXT_AUTH_OKTA_ISSUER')
expect(process.env.NEXT_AUTH_OKTA_CLIENT_SECRET).toBe('NEXT_AUTH_OKTA_CLIENT_SECRET')
expect(process.env.SENTRY_ENVIRONMENT).toBe('SENTRY_ENVIRONMENT')
expect(process.env.SENTRY_PROJECT).toBe('SENTRY_PROJECT')
expect(process.env.SENTRY_URL).toBe('SENTRY_URL')
expect(process.env.SENTRY_ORG).toBe('SENTRY_ORG')
expect(process.env.SENTRY_DSN).toBe('SENTRY_DSN')
});

it('should log an error if a required env var is missing', () => {
Expand All @@ -43,11 +43,11 @@ describe('loadClientEnvVars', () => {
// All environment variables are already defined before the test.
// Their values are then loaded and checked against their expected values.
it('should load and verify environment variables', () => {
const envVars = loadClientEnvVars();
loadClientEnvVars();

expect(envVars.NEXT_PUBLIC_OPENCUE_ENDPOINT).toBe('NEXT_PUBLIC_OPENCUE_ENDPOINT')
expect(envVars.NEXT_PUBLIC_URL).toBe('NEXT_PUBLIC_URL')
expect(envVars.NEXT_PUBLIC_AUTH_PROVIDER).toBe('NEXT_PUBLIC_AUTH_PROVIDER')
expect(process.env.NEXT_PUBLIC_OPENCUE_ENDPOINT).toBe('NEXT_PUBLIC_OPENCUE_ENDPOINT')
expect(process.env.NEXT_PUBLIC_URL).toBe('NEXT_PUBLIC_URL')
expect(process.env.NEXT_PUBLIC_AUTH_PROVIDER).toBe('NEXT_PUBLIC_AUTH_PROVIDER')
});

it('should log an error if a required env var is missing', () => {
Expand Down
4 changes: 2 additions & 2 deletions cueweb/app/api/countlines/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { exec as execCallback } from "child_process";
import { promisify } from "util";
import * as Sentry from "@sentry/nextjs";
import { handleError } from "@/app/utils/notify_utils";

const exec = promisify(execCallback);
async function countLines(filename: string | null) {
Expand All @@ -12,7 +12,7 @@ async function countLines(filename: string | null) {
}
return parseInt(result.stdout.trim().split(" ")[0]);
} catch (error) {
Sentry.captureMessage(`Error reading logfile: ${error}`, "log");
handleError(`Error reading logfile: ${error}`);
return -1;
}
}
Expand Down
4 changes: 2 additions & 2 deletions cueweb/app/api/getlines/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { exec as execCallback } from "child_process";
import { promisify } from "util";
import * as Sentry from "@sentry/nextjs";
import { handleError } from "@/app/utils/notify_utils";

const exec = promisify(execCallback);

Expand All @@ -21,7 +21,7 @@ export async function GET(request: NextRequest) {
let path = request.nextUrl.searchParams.get("path");

if (!path || !start || !end) {
Sentry.captureMessage("Query paramater not provided to api/getlines", "error");
handleError("Query paramater not provided to api/getlines");
return NextResponse.json({ message: "Query parameter not provided" }, { status: 500 });
}

Expand Down
6 changes: 3 additions & 3 deletions cueweb/app/api/increment/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import MetricsService from "@/lib/metrics-service";
import * as Sentry from "@sentry/nextjs";
import { handleError } from "@/app/utils/notify_utils";

// Endpoint to increment Prometheus metric - username counter
export async function GET(request: NextRequest) {
Expand All @@ -24,7 +24,7 @@ export async function GET(request: NextRequest) {
return new NextResponse('Metric incremented successfully for user: ' + username, { status: 200 });
} catch (error) {
// Log the error and return an HTTP 500 response if an error occurs
Sentry.captureMessage(`Error incrementing metric for username: ${username}\nError: ${error}`, "error")
handleError(`Error incrementing metric for username: ${username}\nError: ${error}`)
return new NextResponse('Error in processing your request', { status: 500 });
}
}
}
6 changes: 3 additions & 3 deletions cueweb/app/api/metrics/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import MetricsService from "@/lib/metrics-service";
import * as Sentry from "@sentry/nextjs";
import { handleError } from "@/app/utils/notify_utils";

//endpoint to return prometheus metrics
export async function GET(request: NextRequest) {
Expand All @@ -17,7 +17,7 @@ export async function GET(request: NextRequest) {
}
});
} catch (error) {
Sentry.captureMessage(`Failed to retrieve metrics: ${error}`, "error");
handleError(`Failed to retrieve metrics: ${error}`);
return new Response('Internal server error', { status: 500 });
}
}
}
36 changes: 19 additions & 17 deletions cueweb/app/jobs/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,20 @@ import {
} from "@tanstack/react-table";
import debounce from "lodash/debounce";
import { ChevronDown } from "lucide-react";
import { Session } from "next-auth";
import { signOut } from "next-auth/react";
import { useTheme } from "next-themes";
import * as React from "react";
import { useEffect, useReducer } from "react";
import { MdOutlineCancel } from "react-icons/md";
import { TbEyeOff, TbPacman, TbPlayerPause, TbPlayerPlay, TbReload } from "react-icons/tb";
import CueWebIcon from "../../components/ui/cuewebicon";
import { UNKNOWN_USER } from "@/app/utils/constants";
import { getState, Job } from "./columns";
import "./index.css";

interface DataTableProps {
columns: ColumnDef<Job>[];
data: Job[];
session: Session;
username: string;
}

// Define the actions for useReducer
Expand Down Expand Up @@ -137,7 +137,7 @@ const initialState: State = {
rowSelection: {},
columnVisibility: getItemFromLocalStorage("columnVisibility", JSON.stringify(initialColumnVisibility)),
error: null,
username: "Unknown User",
username: UNKNOWN_USER,
};

// Reducer function
Expand Down Expand Up @@ -211,7 +211,7 @@ const JobActionButton = ({

);

export function DataTable({ columns, data, session }: DataTableProps) {
export function DataTable({ columns, username }: DataTableProps) {
const { theme, setTheme } = useTheme();

// useReducer hook to manage state
Expand Down Expand Up @@ -250,7 +250,6 @@ export function DataTable({ columns, data, session }: DataTableProps) {
}, [state.stateSelectValue]);

useEffect(() => {
const username: string = (session && session.user && session.user.email) ? session.user.email.split("@")[0] : "Unknown User";
dispatch({ type: "SET_USERNAME", payload: username });
}, []);

Expand Down Expand Up @@ -623,7 +622,7 @@ export function DataTable({ columns, data, session }: DataTableProps) {
};

async function addUsersJobs() {
if (!state.autoloadMine || state.username === "Unknown User") return;
if (!state.autoloadMine || state.username === UNKNOWN_USER) return;

const userJobs = await getJobsForUser(state.username);

Expand Down Expand Up @@ -701,16 +700,19 @@ export function DataTable({ columns, data, session }: DataTableProps) {
<div className="flex items-center justify-between px-1 py-4">
<CueWebIcon />
<div className="flex flex-row space-x-2">
<Button
onClick={() => {
localStorage.removeItem("tableData");
localStorage.removeItem("tableDataUnfiltered");
// @ts-ignore
signOut("okta");
}}
>
Signout ({state.username})
</Button>
{
username !== UNKNOWN_USER &&
<Button
onClick={() => {
localStorage.removeItem("tableData");
localStorage.removeItem("tableDataUnfiltered");
// @ts-ignore
signOut("okta");
}}
>
Signout ({state.username})
</Button>
}
<ThemeToggle></ThemeToggle>
</div>
</div>
Expand Down
Loading

0 comments on commit 9c326b6

Please sign in to comment.