Skip to content

Commit

Permalink
Integrate the dashboard with Pelorus using the new pelorus-api, and s…
Browse files Browse the repository at this point in the history
…et it up to be deployable to OpenShift. (#3)

* keycloak init

* update with KeyCloak

* wip add appList

* wip integrate DORA metrics

* wip 4 DORA working

* wip not working

* Working

* Refactor to have useEffect retrigger dashboard components

* chart for LTFC

* sort dates in LTFC

* wip for ltfc table

* add table to LTFC

* Add percent change badges for ltfc and df

* fix useState inits, and fix missing key in table

* Add a github action to build the app into a NodeJS Image

* Update trigger

* trigger on pr

* Change namespace

* Try vars instead of env

* Add dev tag

* wip add openshift oauth

* wip OpenShift OAuth - hardcoded URLs

* externalize OpenShift OAuth Config

* remove comments from options.js

* update scope to user:info

* Updated measure tabs to recalculate on date selection change

* For now, go to 5 decimal places

* Update to pass a timestamp along with a date range to the api call

* updated to pass  query param per new api updates. Date picker now works

* Finally got the bug fixed, using a custom event handler

* OK, date picker minimally works now

* Remove unused code

* Refactor to store the selected date in the browser cache so it survives a refresh

* Add a few helpful comments

* Get chart and table populating

* Further refactors

* fix issue with locality in the chart rendering

* Code changes for enabling the MTTR chart and table, plus icon improvements

* Enable chart and table for change failure rate

* Get change sine list badges working for mttr and cfr

* Modify the 'to' date to the end of the day (i.e. 23:59:59) instead of the start so we get data for that day (asuwebdesign#8)

* Refactor to gather all the API calls into one place (asuwebdesign#9)

* Refactor to gather all the API calls into one place

* Fix spacing

* Fix ESlint errors

* Add a login with openshift option to dashboard homepage

* Add multi-stage build containerfile for packaging dashboard in a more opinionated manner

* Allow package-lock.json to be committed

* Add deployment files and muck with a mysterious logo render issue

* Add a local user/pass mechanism that will only redner for local development

* Add commit_link to the ltfc table

* Add labelling for commit data to the image

* Starting a refactor of the dashboard to make it a server-side component, moving client side state handling into child components

* Ensure user selections are not wiped out when page reloads

* Convert ltfc tab trigger

* Convert deployment-frequency data components

* df tables does not need appname prop

* Nevermind, i guess we do

* Convert MTTR to new component structure

* Add change failure rate in new component format

* Fix openshift sign-in logo

* Display image sha in lead time for change data table to add context for the user

---------

Co-authored-by: Charro Gruver <null>
Co-authored-by: Charro Gruver <39659182+cgruver@users.noreply.github.com>
  • Loading branch information
etsauer and cgruver authored Aug 22, 2024
1 parent 939dd8b commit bc0eb3f
Show file tree
Hide file tree
Showing 69 changed files with 5,430 additions and 3,019 deletions.
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Recommended to have .dockerignore file with the following content
Containerfile
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git
46 changes: 46 additions & 0 deletions .github/workflows/build-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---

name: CD

on: [push, pull_request]


jobs:

build_image:

# The type of runner that the job will run on
runs-on: ubuntu-latest

# This workflow builds a container image of a java
# application using the source to image build strategy,
# and pushes the image to quay.io.
env:
IMAGE_NAME: pelorus-dashboard
TAGS: dev ${{ github.sha }}

steps:

- name: Checkout
uses: actions/checkout@v2

# Setup S2i and Build container image
- name: Setup and Build
id: build_image
uses: redhat-actions/s2i-build@v2
with:
path_context: '.'
# Builder image for a java project
builder_image: 'registry.access.redhat.com/ubi9/nodejs-18:1'
image: ${{ env.IMAGE_NAME }}
tags: ${{ env.TAGS }}

# Push Image to Quay registry
- name: Push To Quay Action
uses: redhat-actions/push-to-registry@v2
with:
image: ${{ steps.build_image.outputs.image }}
tags: ${{ steps.build_image.outputs.tags }}
registry: quay.io/${{ vars.QUAY_NAMESPACE }}
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
66 changes: 66 additions & 0 deletions Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# adapted from https://github.com/vercel/next.js/tree/canary/examples/with-docker

# Install dependencies only when needed
FROM registry.access.redhat.com/ubi9/nodejs-20 AS deps
USER 0
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
else echo "Lockfile not found." && exit 1; \
fi

# Rebuild the source code only when needed
FROM registry.access.redhat.com/ubi9/nodejs-20 AS builder
USER 0
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
ENV NEXT_TELEMETRY_DISABLED 1

# If using yarn uncomment out and comment out npm below
# RUN yarn build

# If using npm comment out above and use below instead
RUN npm run build

# Production image, copy all the files and run next
FROM registry.access.redhat.com/ubi9/nodejs-20-minimal AS runner
USER 0
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to enable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1

COPY --from=builder /app/public ./public

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=1001:1001 /app/.next/standalone ./
COPY --from=builder --chown=1001:1001 /app/.next/static ./.next/static

USER 1001

EXPOSE 3000

ENV PORT 3000
ENV NEXT_PUBLIC_PELORUS_API_URL https://pelorus-api-pelorus-api.apps.cluster-kwcgn.kwcgn.sandbox558.opentlc.com

ARG ORIGIN_URL=unknown
ARG COMMIT_DATE=unknown
ARG COMMIT_ID=unknown

LABEL io.openshift.build.source-location=${ORIGIN_URL}
LABEL io.openshift.build.commit.date=${COMMIT_DATE}
LABEL io.openshift.build.commit.id=${COMMIT_ID}

CMD ["node", "server.js"]
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ First, you'll want to store environment variables locally so you can authenticat
```bash
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=
GITHUB_ID=
GITHUB_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
```

To create a `NEXTAUTH_SECRET` you can refer to [NextAuth options](https://next-auth.js.org/configuration/options)
Expand All @@ -41,11 +37,15 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the

### Sign in

The following sign in providers are supported:
## Building the Code

- `GitHub`
- `Google (Red Hat accounts only)`
```
podman build -f Containerfile -t quay.io/etsauer/pelorus-ui:dev --build-arg "COMMIT_ID=$(git rev-parse HEAD)" --build-arg "COMMIT_DATE=$(git log -1 --format='%ad' --date='format:%a %b %d %H:%M:%S %Y %z')" --build-arg "ORIGIN_URL=$(git config --get remote.origin.url)"
podman push quay.io/etsauer/pelorus-ui:dev
```

## Deployment
## Deployment in OpenShift

The application is deployed on [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) using auto deployment scripts. The production environment can be viewed at [https://developer-intelligence.vercel.app/](https://developer-intelligence.vercel.app/)
```
oc apply -f manifests/
```
6 changes: 6 additions & 0 deletions app/api/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use server'
import { signIn } from 'next-auth/react'

export async function signInUser (formData) {
signIn("credentials", formData)
}
80 changes: 68 additions & 12 deletions app/api/auth/[...nextauth]/options.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,77 @@
import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"
import GoogleProvider from "next-auth/providers/google"
import CredentialsProvider from "next-auth/providers/credentials"

export const options = {
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
async function makeUserinfoRequest(context) {
console.log(context)

const res = await fetch(
'https://kubernetes.default.svc/apis/user.openshift.io/v1/users/~',
{
headers: new Headers({
'Authorization': 'Bearer '+context.tokens.access_token,
'Content-Type': 'application/json'
}),
}
)

return res.json()
}

const providers = [
{
id: "openshift",
name: "OpenShift",
type: "oauth",
wellKnown: "https://openshift.default.svc/.well-known/oauth-authorization-server",
authorization: { params: { scope: "user:info" } },
userinfo: {
url: "https://kubernetes.default.svc/apis/user.openshift.io/v1/users/~",
// The result of this method will be the input to the `profile` callback.
async request(context) {
// context contains useful properties to help you make the request.
return await makeUserinfoRequest(context)
}
},
profile(profile) {
return {
id: profile.metadata.uid,
username: profile.metadata.name,
name: profile.metadata.name,
email: profile.metadata.name,
groups: profile.groups
}
},
clientId: process.env.OPENSHIFT_CLIENT_ID, // this should be a service account with name format system:serviceaccount:<namespace>:<serviceaccount name>
clientSecret: process.env.OPENSHIFT_CLIENT_SECRET // this is the service account token, which can be extracted from a secret
}
]

// Conditionally add the basic auth provider in development mode
if (process.env.NODE_ENV === "development") {
providers.push(
CredentialsProvider({
id: "local",
name: "Local",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
authorize(credentials) {
if (credentials.username === "pelorus" && credentials.password === "pelorus") {
return { id: 1, name: "pelorus", email: "pelorus@example.com" }
}
return null
}
})
],
)
}

export const options = {
providers,
pages: {
signIn: '/',
signOut: '/',
}
}

export default NextAuth(options)
52 changes: 52 additions & 0 deletions app/api/pelorus-api.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { getDaysBetweenDates } from '@/lib/date-funcs';

// Get initial list of Apps that Pelorus has data for
export async function getApps() {
const response = await fetch(`${process.env.NEXT_PUBLIC_PELORUS_API_URL}/sdp/apps?range=1w`)
if (!response.ok) {
throw new Error('Failed to fetch list of Apps from Pelorus')
}
return response.json()
}

// Lead time for change
export async function fetchLeadTimeForChangeData(appName, dateRange) {
const req = `${process.env.PELORUS_API_URL}/sdp/lead_time_for_change/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${dateRange.to.getTime() / 1000}`;
console.log(req)
const response = await fetch(req);

if (!response.ok) {
throw new Error("Failed to fetch Lead Time for Change data");
}

const data = await response.json();
return data.sort((d1, d2) => (d1.timestamp > d2.timestamp) ? 1 : (d1.timestamp < d2.timestamp) ? -1 : 0);
}

// Change Failure Rate
export async function fetchChangeFailureRateData(appName, dateRange) {
const req = `${process.env.PELORUS_API_URL}/sdp/change_failure_rate/${appName}/data?range=${getDaysBetweenDates(dateRange)}d&start=${Math.floor(dateRange.to.getTime() / 1000)}`;
console.log(req)
const response = await fetch(req);

if (!response.ok) {
throw new Error("Failed to fetch Change Failure Rate data");
}

const data = await response.json();
return data.sort((d1, d2) => (d1.timestamp > d2.timestamp ? 1 : d1.timestamp < d2.timestamp ? -1 : 0));
}

export function getDORA(appName) {

const dora = {mttr: 0, cfr: 0, df: 0, ltfc: 0}
const cfr = getCFR(appName)
dora.cfr = cfr.cfr
const df = getDF(appName)
dora.df = df.df
const mttr = getMTTR(appName)
dora.mttr = mttr.mttr
const ltfc = getLTFC(appName)
dora.ltfc = ltfc.ltfc
return dora
}
52 changes: 0 additions & 52 deletions app/dashboard-old/page.js

This file was deleted.

Loading

0 comments on commit bc0eb3f

Please sign in to comment.