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

Feature Depends on functionalities and Set mandatory fields via UI Meta flag #187

Open
wants to merge 12 commits into
base: staging
Choose a base branch
from
Open
30 changes: 18 additions & 12 deletions radlab-ui/webapp/__mocks__/terraform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const DATA_SCIENCE_VARS = `

# {{UIMeta group=1 order=1 }}
variable "billing_account_id" {
description = "Billing Account associated to the GCP Resources"
description = "Billing Account associated to the GCP Resources. {{UIMeta group=0 order=3 mandatory }}"
type = string
}

Expand All @@ -21,7 +21,7 @@ variable "boot_disk_type" {
variable "create_container_image" {
description = "If the notebook needs to have image type as Container set this variable to true, set it to false when using dafault image type i.e. VM."
type = bool
default = false
default = true
}

variable "create_network" {
Expand All @@ -36,8 +36,14 @@ variable "create_project" {
default = true
}

variable "create_usermanaged_notebook" {
description = "Set to true if you want to create user managed workbench notebooks. If you want to create google managed workbench notebook, set this variable to false. {{UIMeta group=2 order=1 }}"
type = bool
default = true
}
saurabhkg891 marked this conversation as resolved.
Show resolved Hide resolved

variable "container_image_repository" {
description = "Container Image Repo, only set if creating container image notebook instance by setting \`create_container_image\` variable to true"
description = "Container Image Repo, only set if creating container image notebook instance by setting \`create_container_image\` variable to true. {{UIMeta group=2 order=4 dependson=(create_container_image==true) mandatory }}"
type = string
default = ""
}
Expand Down Expand Up @@ -67,13 +73,13 @@ variable "folder_id" {
}

variable "gpu_accelerator_type" {
description = "Type of GPU you would like to spin up"
description = "Type of GPU you would like to spin up. {{UIMeta group=2 order=10 dependson=(enable_gpu_driver==true) mandatory }}"
type = string
default = ""
}

variable "gpu_accelerator_core_count" {
description = "Number of of GPU core count. {{UIMeta group=3 order=11 }}"
description = "Number of of GPU core count"
type = number
default = 0
}
Expand All @@ -91,19 +97,19 @@ variable "image_project" {
}

variable "ip_cidr_range" {
description = "Unique IP CIDR Range for AI Notebooks subnet"
description = "Unique IP CIDR Range for AI Notebooks subnet {{UIMeta group=3 order=5 dependson=(create_network==true&&create_usermanaged_notebook==true) mandatory}}"
type = string
default = "10.142.190.0/24"
}

variable "machine_type" {
description = "Type of VM you would like to spin up"
description = "Type of VM you would like to spin up.{{UIMeta group=3 order=5 dependson=(create_network==true&&enable_gpu_driver==true)}}"
type = string
default = "n1-standard-1"
}

variable "network_name" {
description = "Name of the network to be created."
description = "Name of the network to be created. {{UIMeta group=3 order=2 dependson=(create_usermanaged_notebook==true||enable_gpu_driver==true) mandatory}}"
type = string
default = "ai-notebook"
}
Expand Down Expand Up @@ -139,7 +145,7 @@ variable "set_external_ip_policy" {
}

variable "set_shielded_vm_policy" {
description = "Apply org policy to disable shielded VMs. {{UIMeta updatesafe}}"
description = "Apply org policy to disable shielded VMs. {{UIMeta dependson=(set_external_ip_policy==true||enable_gpu_driver==true) updatesafe}}"
type = bool
default = true
}
Expand All @@ -151,7 +157,7 @@ variable "set_trustedimage_project_policy" {
}

variable "subnet_name" {
description = "Name of the subnet where to deploy the Notebooks. {{UIMeta group=3 }}"
description = "Name of the subnet where to deploy the Notebooks. {{UIMeta group=3 dependson=(enable_gpu_driver==true||create_usermanaged_notebook==true&&create_network==true||set_external_ip_policy==true) mandatory}}"
type = string
default = "subnet-ai-notebook"
}
Expand All @@ -163,7 +169,7 @@ variable "trusted_users" {
}

variable "zone" {
description = "Cloud Zone associated to the AI Notebooks {{UIMeta group=1 order=1 options=us-central1-b,us-east1-a,us-west3-b,us-east4-c }}"
description = "Cloud Zone associated to the AI Notebooks {{UIMeta group=1 order=1 options=us-central1-b,us-east1-a,us-west3-b,us-east4-c mandatory }}"
type = string
default = "us-east4-c"
otherfield = "bar"
Expand All @@ -186,7 +192,7 @@ variable "billing_budget_alert_spent_percents" {
}

variable "billing_budget_services" {
description = "A list of services ids to be included in the budget. If omitted, all services will be included in the budget. Service ids can be found at https://cloud.google.com/skus/. {{UIMeta group=0 order=12 updatesafe }}"
description = "A list of services ids to be included in the budget. If omitted, all services will be included in the budget. Service ids can be found at https://cloud.google.com/skus/. {{UIMeta group=0 order=12 updatesafe dependson=(enable_gpu_driver==true||set_external_ip_policy==true&&create_network==true||create_usermanaged_notebook==true)}}"
type = list(string)
default = null
}
Expand Down
170 changes: 160 additions & 10 deletions radlab-ui/webapp/__tests__/utils/terraform.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { parseVarsFile, groupVariables } from "@/utils/terraform"
import {
parseVarsFile,
groupVariables,
checkDependsOnValid,
initialFormikData,
} from "@/utils/terraform"
import { DATA_SCIENCE_VARS } from "@/mocks/terraform"
import { IUIVariable } from "@/utils/types"

Expand Down Expand Up @@ -121,16 +126,17 @@ describe("terraform util", () => {
const parsed = parseVarsFile(DATA_SCIENCE_VARS)

const billing_account_id = getByName("billing_account_id", parsed)
expect(billing_account_id.default).toBeNull()
expect(billing_account_id.mandatory).toStrictEqual(true)
expect(billing_account_id.required).toBe(true)

const organization_id = getByName("organization_id", parsed)
expect(organization_id.default).toBe("")
expect(organization_id.mandatory).toStrictEqual(false)
expect(organization_id.required).toBe(false)

const zone = getByName("zone", parsed)
expect(zone.default).not.toBeNull()
expect(zone.default).toStrictEqual("us-east4-c")
expect(zone.mandatory).toStrictEqual(true)
expect(zone.required).toBe(true)
})

Expand Down Expand Up @@ -174,13 +180,6 @@ describe("terraform util", () => {
const set_external_ip_policy = getByName("set_external_ip_policy", parsed)
expect(set_external_ip_policy.default).not.toBeNull()
expect(set_external_ip_policy.default).toBe(false)

const gpu_accelerator_core_count = getByName(
"gpu_accelerator_core_count",
parsed,
)
expect(gpu_accelerator_core_count.default).not.toBeNull()
expect(gpu_accelerator_core_count.default).toStrictEqual(0)
})

describe("updatesafe parsing", () => {
Expand Down Expand Up @@ -230,4 +229,155 @@ describe("terraform util", () => {
expect(billing_budget_alert_spent_percents.group).toStrictEqual(0)
})
})

describe("depends on variables", () => {
it("single vars depends on", () => {
const parsed = parseVarsFile(DATA_SCIENCE_VARS)
const varsAnswerData = initialFormikData(parsed)
const create_container_image = getByName("create_container_image", parsed)
expect(create_container_image.default).not.toBeNull()
expect(create_container_image.default).toStrictEqual(true)

const container_image_repository = getByName(
"container_image_repository",
parsed,
)
expect(container_image_repository).toHaveProperty("dependsOn")
const isDependOnValid = checkDependsOnValid(
container_image_repository.dependsOn,
varsAnswerData,
)
expect(isDependOnValid).toBe(true)

const enable_gpu_driver = getByName("enable_gpu_driver", parsed)
expect(enable_gpu_driver.default).not.toBeNull()
expect(enable_gpu_driver.default).toStrictEqual(false)

const gpu_accelerator_type = getByName("gpu_accelerator_type", parsed)
expect(gpu_accelerator_type).toHaveProperty("dependsOn")
const isDependOnValidAcceleratorType = checkDependsOnValid(
gpu_accelerator_type.dependsOn,
varsAnswerData,
)
expect(isDependOnValidAcceleratorType).toBe(false)
})

it("multiple vars depends on AND operand", () => {
const parsed = parseVarsFile(DATA_SCIENCE_VARS)
const varsAnswerData = initialFormikData(parsed)
const create_network = getByName("create_network", parsed)
expect(create_network.default).not.toBeNull()
expect(create_network.default).toStrictEqual(true)

const create_usermanaged_notebook = getByName(
"create_usermanaged_notebook",
parsed,
)
expect(create_usermanaged_notebook.default).not.toBeNull()
expect(create_usermanaged_notebook.default).toStrictEqual(true)

const ip_cidr_range = getByName("ip_cidr_range", parsed)
expect(ip_cidr_range).toHaveProperty("dependsOn")

const isDependOnValid = checkDependsOnValid(
ip_cidr_range.dependsOn,
varsAnswerData,
)
expect(isDependOnValid).toBe(true)

const enable_gpu_driver = getByName("enable_gpu_driver", parsed)
expect(enable_gpu_driver.default).not.toBeNull()
expect(enable_gpu_driver.default).toStrictEqual(false)

const machine_type = getByName("machine_type", parsed)
expect(machine_type).toHaveProperty("dependsOn")

const isDependOnValidMachineType = checkDependsOnValid(
machine_type.dependsOn,
varsAnswerData,
)
expect(isDependOnValidMachineType).toBe(false)
})

it("multiple vars depends on OR operand", () => {
const parsed = parseVarsFile(DATA_SCIENCE_VARS)
const varsAnswerData = initialFormikData(parsed)
const enable_gpu_driver = getByName("enable_gpu_driver", parsed)
expect(enable_gpu_driver.default).not.toBeNull()
expect(enable_gpu_driver.default).toStrictEqual(false)

const create_usermanaged_notebook = getByName(
"create_usermanaged_notebook",
parsed,
)
expect(create_usermanaged_notebook.default).not.toBeNull()
expect(create_usermanaged_notebook.default).toStrictEqual(true)

const network_name = getByName("network_name", parsed)
expect(network_name).toHaveProperty("dependsOn")

const isDependOnValid = checkDependsOnValid(
network_name.dependsOn,
varsAnswerData,
)
expect(isDependOnValid).toBe(true)

const set_external_ip_policy = getByName("set_external_ip_policy", parsed)
expect(set_external_ip_policy.default).not.toBeNull()
expect(set_external_ip_policy.default).toStrictEqual(false)

const set_shielded_vm_policy = getByName("set_shielded_vm_policy", parsed)
expect(set_shielded_vm_policy).toHaveProperty("dependsOn")

const isDependOnValidVmPolicy = checkDependsOnValid(
set_shielded_vm_policy.dependsOn,
varsAnswerData,
)
expect(isDependOnValidVmPolicy).toBe(false)
})

it("multiple vars depends on OR and AND operand", () => {
const parsed = parseVarsFile(DATA_SCIENCE_VARS)
const varsAnswerData = initialFormikData(parsed)
const enable_gpu_driver = getByName("enable_gpu_driver", parsed)
expect(enable_gpu_driver.default).not.toBeNull()
expect(enable_gpu_driver.default).toStrictEqual(false)

const create_usermanaged_notebook = getByName(
"create_usermanaged_notebook",
parsed,
)
expect(create_usermanaged_notebook.default).not.toBeNull()
expect(create_usermanaged_notebook.default).toStrictEqual(true)

const create_network = getByName("create_network", parsed)
expect(create_network.default).not.toBeNull()
expect(create_network.default).toStrictEqual(true)

const set_external_ip_policy = getByName("set_external_ip_policy", parsed)
expect(set_external_ip_policy.default).not.toBeNull()
expect(set_external_ip_policy.default).toStrictEqual(false)

const subnet_name = getByName("subnet_name", parsed)
expect(subnet_name).toHaveProperty("dependsOn")

const isDependOnValid = checkDependsOnValid(
subnet_name.dependsOn,
varsAnswerData,
)
expect(isDependOnValid).toBe(true)

const billing_budget_services = getByName(
"billing_budget_services",
parsed,
)
expect(billing_budget_services).toHaveProperty("dependsOn")

const isDependOnValidBudgetService = checkDependsOnValid(
billing_budget_services.dependsOn,
varsAnswerData,
)
expect(isDependOnValidBudgetService).toBe(false)
})
})
})
27 changes: 23 additions & 4 deletions radlab-ui/webapp/src/components/forms/CreateForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import React, { useState, useEffect } from "react"
import { FormikStepper } from "@/components/forms/FormikStepper"
import StepCreator from "@/components/forms/StepCreator"
import { IUIVariable, Deployment, DEPLOYMENT_STATUS } from "@/utils/types"
import { groupVariables, initialFormikData } from "@/utils/terraform"
import {
formatRelevantVariables,
groupVariables,
initialFormikData,
} from "@/utils/terraform"
import axios from "axios"
import { useNavigate, useParams } from "react-router-dom"
import { alertStore, userStore } from "@/store"
Expand All @@ -11,6 +15,7 @@ import { useTranslation } from "next-i18next"
import Loading from "@/navigation/Loading"
import UpdateSafeDeploymentModal from "@/components/UpdateSafeDeploymentModal"
import { whereEq } from "ramda"
import { FormikValues } from "formik"

interface CreateForm {
formVariables: IUIVariable[]
Expand Down Expand Up @@ -43,6 +48,7 @@ const CreateForm: React.FC<CreateForm> = ({
const [loading, setLoading] = useState(true)
const [modalUpdateSafe, setUpdateSafeModal] = useState(false)
const [modalUpdateSafePayload, setUpdateSafePayload] = useState({})
const [answerValueData, setAnswerValueData] = useState<FormikValues>({})

if (!user) throw new Error("No signed in user")

Expand Down Expand Up @@ -187,14 +193,22 @@ const CreateForm: React.FC<CreateForm> = ({
}
}

const handleChangeValues = (answerValues: FormikValues) => {
setAnswerValueData(answerValues)
}

useEffect(() => {
if (formVariables.length > 0) {
const groupedVariableList = groupVariables(formVariables)
const relevantFormVariables = formatRelevantVariables(
formVariables,
answerValueData,
)
const groupedVariableList = groupVariables(relevantFormVariables)
const groupedVariableListFilter = removeAdminData(groupedVariableList)
setFormData(groupedVariableListFilter)
initialFormikDefaultData(groupedVariableListFilter)
}
}, formVariables)
}, [formVariables, answerValueData])

if (loading) return <Loading />

Expand All @@ -209,7 +223,12 @@ const CreateForm: React.FC<CreateForm> = ({
{Object.keys(formData).map((grpId, index) => {
const group: undefined | IUIVariable[] = formData[grpId]
return group ? (
<StepCreator variableList={group} idx={index} key={index} />
<StepCreator
variableList={group}
idx={index}
key={index}
handleChangeValues={handleChangeValues}
/>
) : (
<></>
)
Expand Down
Loading