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

feat(web): quality assurance #874

Merged
merged 17 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,12 @@ DISABLE_RECAPTCHA=false
# It is recommended to use the inivisble type
RECAPTCHA_SECRET_KEY=Recaptcha_Secret_Key
RECAPTCHA_SITE_KEY=Recaptcha_Site_Key

# To turn off quality assurance
# defaults to false
# TODO (@gerimate): Replace the docs url
# more info: https://docs.dyrector.io/qa
# QA_OPT_OUT=true

# For providing a group identifier codename for the collected usage data
# QA_GROUP_NAME=
1 change: 1 addition & 0 deletions docker-compose.http-only.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ services:
- DISABLE_RECAPTCHA=${DISABLE_RECAPTCHA}
- RECAPTCHA_SECRET_KEY=${RECAPTCHA_SECRET_KEY}
- RECAPTCHA_SITE_KEY=${RECAPTCHA_SITE_KEY}
- QA_OPT_OUT=${QA_OPT_OUT:-false}
depends_on:
crux-postgres:
condition: service_healthy
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ services:
- DISABLE_RECAPTCHA=${DISABLE_RECAPTCHA}
- RECAPTCHA_SECRET_KEY=${RECAPTCHA_SECRET_KEY}
- RECAPTCHA_SITE_KEY=${RECAPTCHA_SITE_KEY}
- QA_OPT_OUT=${QA_OPT_OUT:-false}
depends_on:
crux-postgres:
condition: service_healthy
Expand Down
27 changes: 27 additions & 0 deletions web/crux-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/crux-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"next": "^13.5.4",
"next-translate": "2.5.2",
"openpgp": "^5.10.1",
"posthog-js": "^1.85.2",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-datepicker": "^4.16.0",
Expand Down
148 changes: 148 additions & 0 deletions web/crux-ui/quality-assurance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { API_QUALITY_ASSURANCE } from '@app/routes'
import { getCruxFromContext } from '@server/crux-api'
import { GetServerSidePropsContext } from 'next'
import posthog from 'posthog-js'
import { ReactHTML } from 'react'

export const QA_URL = 'https://eu.posthog.com'
export const QA_GROUP_TYPE = 'dyoInstance'

export const QA_SETTINGS_PROP = 'qaSettings'
export type QASettings = {
groupId: string
groupName: string
}

export type QAGroupProperties = {
name: string
}

export type QAElementType = keyof Pick<ReactHTML, 'a' | 'div' | 'button' | 'img' | 'select'>
export type QAElementEvent = {
label: string
elementType: QAElementType
}

const QA_EVENT_CLICK = 'click'
export const sendQAClickEvent = (event: QAElementEvent) => posthog.capture(QA_EVENT_CLICK, event)

const QA_EVENT_KEY = 'key'
export type QAKeyEvent = {
label: string
key: string
}

export const sendQAKeyEvent = (event: QAKeyEvent) => posthog.capture(QA_EVENT_KEY, event)

const QA_EVENT_CHECK = 'check'
const QA_EVENT_UNCHECK = 'uncheck'
export const sendQACheckEvent = (event: QAElementEvent, checked: boolean) =>
posthog.capture(checked ? QA_EVENT_CHECK : QA_EVENT_UNCHECK, event)

const QA_EVENT_TOGGLE = 'toggle'
export const sendQAToggleEvent = (label: string) => {
const event: QAElementEvent = {
elementType: 'button',
label,
}

posthog.capture(QA_EVENT_TOGGLE, event)
}

const QA_EVENT_SELECT_RADIO_BUTTON = 'select-radio-button'
export const sendQASelectRadioButtonEvent = (label: string) => {
const event: QAElementEvent = {
elementType: 'div',
label,
}

posthog.capture(QA_EVENT_SELECT_RADIO_BUTTON, event)
}

const QA_EVENT_SELECT_CHIP = 'select-chip'
export const sendQASelectChipEvent = (label: string) => {
const event: QAElementEvent = {
elementType: 'button',
label,
}

posthog.capture(QA_EVENT_SELECT_CHIP, event)
}

const QA_EVENT_SELECT_ICON = 'select-icon'
export const sendQASelectIconEvent = (icon: string) => {
const event: QAElementEvent = {
elementType: 'img',
label: icon,
}

posthog.capture(QA_EVENT_SELECT_ICON, event)
}

const QA_EVENT_DIALOG = 'dialog'
type DialogEventType = 'open' | 'close'
export const sendQADialogEvent = (label: string, eventType: DialogEventType) => {
const event: QAElementEvent = {
elementType: 'div',
label,
}

posthog.capture(`${QA_EVENT_DIALOG}-${eventType}`, event)
}

export const QA_DIALOG_LABEL_DELETE_CONFIG_BUNDLE = 'deleteConfigBundle'
export const QA_DIALOG_LABEL_REVOKE_NODE_TOKEN = 'revokeNodeToken'
export const QA_DIALOG_LABEL_KICK_AGENT = 'kickAgent'
export const QA_DIALOG_LABEL_DELETE_CONTAINER = 'deleteContainer'
export const QA_DIALOG_LABEL_SET_AS_DEFAULT = 'setAsDefault'
export const QA_DIALOG_LABEL_DEPLOY = 'deploy'
export const QA_DIALOG_LABEL_DELETE_DEPLOYMENT = 'deleteDeployment'
export const QA_DIALOG_LABEL_DELETE_IMAGE = 'deleteImage'
export const QA_DIALOG_LABEL_REVOKE_DEPLOY_TOKEN = 'revokeDeployToken'
export const QA_DIALOG_LABEL_DELETE_FROM_PAGE_MENU = 'deleteFromPageMenu'
export const QA_DIALOG_LABEL_CHANGE_TEAM_SLUG = 'changeTeamSlug'
export const QA_DIALOG_LABEL_LEAVE_TEAM = 'leaveTeam'
export const QA_DIALOG_LABEL_UPDATE_USER_ROLE = 'updateUserRole'
export const QA_DIALOG_LABEL_DEPLOY_PROTECTED = 'deployProtected'
export const QA_DIALOG_LABEL_HIDE_ONBOARDING = 'hideOnboarding'
export const QA_DIALOG_LABEL_CONVERT_PROJECT_TO_VERSIONED = ''
export const QA_DIALOG_LABEL_REMOVE_OIDC_ACCOUNT = 'removeOIDCAccount'
export const QA_DIALOG_LABEL_DELETE_USER_TOKEN = 'deleteUserToken'
export const QA_DIALOG_LABEL_DELETE_USER = 'deleteUser'

export const QA_MODAL_LABEL_NODE_AUDIT_DETAILS = 'nodeAuditDetails'
export const QA_MODAL_LABEL_DEPLOYMENT_NOTE = 'deploymentNote'
export const QA_MODAL_LABEL_IMAGE_TAGS = 'image-tags'
export const QA_MODAL_LABEL_AUDIT_LOG_DETAILS = 'auditLogDetails'

export type QualityAssurance = {
disabled: boolean
group?: {
id: string
name: string
}
}

type QAAwareHttpServer = {
qa: QualityAssurance
}

export const fetchQualityAssuranceSettings = async (context: GetServerSidePropsContext): Promise<QASettings | null> => {
const httpServer: QAAwareHttpServer = (context.req.socket as any).server
if (!httpServer.qa) {
httpServer.qa = await getCruxFromContext<QualityAssurance>(context, API_QUALITY_ASSURANCE)
}

const { qa } = httpServer

if (!qa || !qa.group) {
return null
}

const { id, name } = qa.group

return {
groupId: id,
groupName: name,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const ConfigBundleCard = (props: ConfigBundleCardProps) => {
{titleHref ? <Link href={titleHref}>{title}</Link> : title}

<DyoExpandableText
name="name"
text={configBundle.description}
lineClamp={2}
className="text-md text-light mt-2 max-h-44"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DyoButton from '@app/elements/dyo-button'
import { DyoConfirmationModal } from '@app/elements/dyo-modal'
import useConfirmation from '@app/hooks/use-confirmation'
import useTranslation from 'next-translate/useTranslation'
import React from 'react'
import { QA_DIALOG_LABEL_DELETE_CONFIG_BUNDLE } from 'quality-assurance'

export type DetailsPageTexts = {
edit?: string
Expand All @@ -29,6 +29,7 @@ export const ConfigBundlePageMenu = (props: ConfigBundlePageMenuProps) => {

const deleteClick = async () => {
const confirmed = await confirmDelete({
qaLabel: QA_DIALOG_LABEL_DELETE_CONFIG_BUNDLE,
title: deleteModalTitle,
description: deleteModalDescription,
confirmText: t('delete'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
) : (
<>
<DyoChips
name="nodes"
choices={nodes ?? []}
converter={(it: DyoNode) => it.name}
selection={nodes.find(it => it.id === formik.values.nodeId)}
Expand All @@ -178,6 +179,7 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
) : (
<>
<DyoChips
name="projects"
choices={projects ?? []}
converter={(it: Project) => it.name}
selection={currentProject}
Expand Down Expand Up @@ -207,6 +209,7 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
currentProject.type === 'versioned' && (
<>
<DyoChips
name="versions"
choices={versions ?? []}
converter={(it: Version) => it.name}
selection={versions?.find(it => it.id === formik.values.versionId)}
Expand Down
1 change: 1 addition & 0 deletions web/crux-ui/src/components/nodes/dyo-node-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const DyoNodeCard = (props: DyoNodeCardProps) => {
)}

<DyoExpandableText
name="description"
text={node.description}
lineClamp={node.address ? 4 : 6}
className="text-md text-light mt-2 max-h-44"
Expand Down
6 changes: 5 additions & 1 deletion web/crux-ui/src/components/nodes/dyo-node-setup.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import DyoButton from '@app/elements/dyo-button'
import { DyoCard } from '@app/elements/dyo-card'
import DyoChips from '@app/elements/dyo-chips'
import DyoChips, { chipsQALabelFromValue } from '@app/elements/dyo-chips'
import DyoForm from '@app/elements/dyo-form'
import { DyoHeading } from '@app/elements/dyo-heading'
import DyoIcon from '@app/elements/dyo-icon'
Expand Down Expand Up @@ -127,6 +127,7 @@ const DyoNodeSetup = (props: DyoNodeSetupProps) => {
</DyoHeading>

<DyoChips
name="nodeType"
className="mb-2 ml-2"
choices={NODE_TYPE_VALUES}
selection={formik.values.type}
Expand All @@ -135,6 +136,7 @@ const DyoNodeSetup = (props: DyoNodeSetupProps) => {
await formik.setFieldValue('type', it, true)
await onTypeChanged(it)
}}
qaLabel={chipsQALabelFromValue}
/>

{node.type === 'docker' && (
Expand Down Expand Up @@ -182,11 +184,13 @@ const DyoNodeSetup = (props: DyoNodeSetupProps) => {
</DyoHeading>

<DyoChips
name="scriptType"
className="mb-2 ml-2"
choices={NODE_INSTALL_SCRIPT_TYPE_VALUES}
selection={formik.values.scriptType}
converter={(it: NodeInstallScriptType) => t(`installScript.${it}`)}
onSelectionChange={it => formik.setFieldValue('scriptType', it, true)}
qaLabel={chipsQALabelFromValue}
/>

<DyoLabel className="text-lg mb-2.5" textColor="text-bright">
Expand Down
3 changes: 3 additions & 0 deletions web/crux-ui/src/components/nodes/edit-node-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import EditNodeCard from './edit-node-card'
import NodeConnectionCard from './node-connection-card'
import useNodeState from './use-node-state'
import { SubmitHook } from '@app/hooks/use-submit'
import { QA_DIALOG_LABEL_KICK_AGENT, QA_DIALOG_LABEL_REVOKE_NODE_TOKEN } from 'quality-assurance'

interface EditNodeSectionProps {
className?: string
Expand Down Expand Up @@ -94,6 +95,7 @@ const EditNodeSection = (props: EditNodeSectionProps) => {

const onRevokeToken = async () => {
const confirmed = await confirmTokenRevoke({
qaLabel: QA_DIALOG_LABEL_REVOKE_NODE_TOKEN,
title: t('tokens:areYouSureRevoke'),
confirmText: t('tokens:revoke'),
confirmColor: 'bg-error-red',
Expand Down Expand Up @@ -126,6 +128,7 @@ const EditNodeSection = (props: EditNodeSectionProps) => {

const onKickAgent = async () => {
const confirmed = await confirmTokenRevoke({
qaLabel: QA_DIALOG_LABEL_KICK_AGENT,
title: t('areYouSureKickAgent'),
description: t('kickingAnAgentWillStopIt'),
confirmText: t('kick'),
Expand Down
Loading
Loading