-
Notifications
You must be signed in to change notification settings - Fork 22
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
Allow for dynamic Cockpit Actions and add support for customizable HTTP request actions #1386
Allow for dynamic Cockpit Actions and add support for customizable HTTP request actions #1386
Conversation
e515201
to
88cfb54
Compare
|
04f6210
to
44cde4a
Compare
Added both: Can be tested with the following files: http-request-action (Arm with HTTP).json |
7bc8f6a
to
284e1ee
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This bottom-divider on the expansion panel could be removed without problem.
If you remove it and set the bottom padding as below, the ExpansionPanel will get centered on the config window:
class="flex-col h-full overflow-y-auto ml-[5px] -mb-[10px] pr-3" :class="interfaceStore.isOnSmallScreen ? 'max-w-[80vw] max-h-[90vh]' : 'max-w-[680px] max-h-[85vh]'" > <ExpansiblePanel no-top-divider no-bottom-divider :is-expanded="!interfaceStore.isOnPhoneScreen">
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep visual consistency, we should keep the dialog modals looking as close as possible to each other.
This visual consistency will be automated soon with BlueVue, but for now things must be manual.
Check this visual diff:
Before
After
This is a good reference for the dialog:
If you want a shortcut, this is how I implemented the example:
`<v-dialog` v-model="actionDialog.show" max-width="500px">
<v-card class="rounded-lg" :style="interfaceStore.globalGlassMenuStyles">
<v-card-title class="text-h6 font-weight-bold py-4 text-center">{{
editMode ? 'Edit action' : 'Create new action'
}}</v-card-title>
<v-card-text class="px-8">
<v-form class="d-flex flex-column gap-2" @submit.prevent="createActionConfig">
<v-text-field
v-model="newActionConfig.name"
label="Action Name"
required
variant="outlined"
density="compact"
></v-text-field>
<v-select
v-model="newActionConfig.method"
:items="availableHttpRequestMethods"
label="Request Type"
required
variant="outlined"
density="compact"
></v-select>
<v-text-field
v-model="newActionConfig.url"
label="URL"
required
variant="outlined"
density="compact"
></v-text-field>
<div class="d-flex align-center justify-space-between">
<h3 class="text-subtitle-2 font-weight-bold">URL Parameters</h3>
<v-btn variant="text" class="px-2 py-1" density="compact" @click="openUrlParamDialog">
<v-icon size="small">mdi-plus</v-icon>
Add
</v-btn>
</div>
<div v-if="Object.keys(newActionConfig.urlParams).length > 0" class="mb-2">
<v-chip-group>
<v-chip
v-for="(param, index) in Object.entries(newActionConfig.urlParams)"
:key="`param-${index}`"
closable
size="x-small"
class="ma-1"
@click:close="removeUrlParam(param[0])"
>
{{ param[0] }}: {{ param[1] }}
</v-chip>
</v-chip-group>
</div>
<div class="d-flex align-center justify-space-between">
<h3 class="text-subtitle-2 font-weight-bold">Headers</h3>
<v-btn variant="text" class="px-2 py-1" density="compact" @click="openHeaderDialog">
<v-icon size="small">mdi-plus</v-icon>
Add
</v-btn>
</div>
<div v-if="Object.keys(newActionConfig.headers).length > 0" class="mb-2">
<v-chip-group>
<v-chip
v-for="(header, index) in Object.entries(newActionConfig.headers)"
:key="`header-${index}`"
closable
size="x-small"
class="ma-1"
@click:close="removeHeader(header[0])"
>
{{ header[0] }}: {{ header[1] }}
</v-chip>
</v-chip-group>
</div>
<div class="d-flex align-center justify-space-between">
<h3 class="text-subtitle-2 font-weight-bold">JSON Body</h3>
<v-btn variant="text" class="px-2 py-1" density="compact" @click="openJsonDialog">
<v-icon size="small">mdi-code-json</v-icon>
Edit
</v-btn>
</div>
</v-form>
</v-card-text>
<v-divider class="mt-2 mx-10" />
<v-card-actions>
<div class="flex justify-between items-center pa-2 w-full h-full">
<v-btn color="white" variant="text" @click="closeActionDialog">Cancel</v-btn>
<div class="flex gap-x-10">
<v-btn variant="text" @click="resetNewAction">Reset</v-btn>
<v-btn color="primary" :disabled="!isFormValid" variant="text" @click="saveActionConfig">
{{ editMode ? 'Save' : 'Create' }}
</v-btn>
</div>
</div>
</v-card-actions>
</v-card>
</v-dialog> `
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the edit JSON body template, there is a modal over another modal, and both are over a configuration floating screen; So, the elements piling up is kinda uncomfortable to the user.
For this case in specific, the solution can be widening the top config modal, so it will stay over the others. Check the result with the element already with the other modifications suggested:
Shortcut code:
<!-- JSON Body Dialog -->
<v-dialog v-model="bodyDialog.show" max-width="520px">
<v-card class="rounded-lg p-3" :style="interfaceStore.globalGlassMenuStyles">
<v-card-title class="text-h6 font-weight-bold pa-2 text-center">Edit JSON body template</v-card-title>
<v-card-text class="px-6">
<v-form class="d-flex flex-column gap-2" @submit.prevent="saveJsonBody">
<v-textarea
v-model="bodyDialog.bodyText"
label="JSON Body Template"
hint="Use {{ anyInputName }} for dynamic values"
persistent-hint
:error-messages="bodyDialog.error"
rows="12"
variant="outlined"
density="compact"
@update:model-value="validateJsonTemplateForDialog"
></v-textarea>
</v-form>
</v-card-text>
<v-divider class="ma-2" />
<v-card-actions class="pa-2 -mb-1">
<div class="flex w-full justify-between">
<v-btn color="white" variant="text" @click="closeJsonDialog">Cancel</v-btn>
<v-btn color="white" :disabled="!bodyDialog.isValid" @click="saveJsonBody">Save</v-btn>
</div>
</v-card-actions>
</v-card>
</v-dialog>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the 'Add header', the solution would be hiding the modal under it (opacity would do it), since it doesn't have enough content to grow vertically.
Also there are some modifications on the content to match the visual identity:
<!-- Header Dialog -->
<v-dialog v-model="headerDialog.show" max-width="400px">
<v-card class="rounded-lg p-3" :style="interfaceStore.globalGlassMenuStyles">
<v-card-title class="text-h6 font-weight-bold pb-4 text-center">Add header</v-card-title>
<v-card-text class="pa-4">
<v-form class="d-flex flex-column gap-2" @submit.prevent="addHeader">
<v-text-field
v-model="headerDialog.key"
label="Header Key"
required
variant="outlined"
:error-messages="headerDialog.error"
density="compact"
></v-text-field>
<v-text-field
v-model="headerDialog.value"
label="Header Value"
variant="outlined"
density="compact"
></v-text-field>
</v-form>
</v-card-text>
<v-divider class="ma-2" />
<v-card-actions class="pa-2 -mb-1">
<div class="flex w-full justify-between">
<v-btn color="white" variant="text" size="small" @click="closeHeaderDialog">Cancel</v-btn>
<v-btn color="white" size="small" @click="addHeader">Save</v-btn>
</div>
</v-card-actions>
</v-card>
</v-dialog>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the 'Url parameters' The solution will have to be the same as before:
Check some mods needed:
Shortcut code:
<!-- URL Parameter Dialog -->
<v-dialog v-model="urlParamDialog.show" max-width="400px">
<v-card class="rounded-lg px-6 py-2" :style="interfaceStore.globalGlassMenuStyles">
<v-card-title class="text-h6 text-center font-weight-bold pb-6">Add URL parameter</v-card-title>
<v-card-text class="pa-2">
<v-form class="d-flex flex-column gap-2" @submit.prevent="addUrlParameter">
<v-text-field
v-model="urlParamDialog.key"
label="Parameter Key"
required
variant="outlined"
density="compact"
></v-text-field>
<v-select
v-model="urlParamDialog.valueType"
:items="paramValueOptions"
label="Parameter Value"
required
variant="outlined"
density="compact"
></v-select>
<v-text-field
v-if="urlParamDialog.valueType === 'hardcoded'"
v-model="urlParamDialog.hardcodedValue"
label="Hardcoded Value"
required
variant="outlined"
density="compact"
></v-text-field>
</v-form>
</v-card-text>
<v-divider class="ma-2" />
<v-card-actions class="pa-2 -mb-1">
<div class="flex w-full justify-between">
<v-btn color="white" variant="text" size="small" @click="closeUrlParamDialog">Cancel</v-btn>
<v-btn color="white" size="small" @click="saveUrlParameter()"> Save </v-btn>
</div>
</v-card-actions>
</v-card>
</v-dialog>
src/libs/actions/data-lake.ts
Outdated
export const getCockpitActionParametersInfo = (id: string): CockpitActionParameter | undefined => { | ||
return cockpitActionParametersInfo[id] | ||
} | ||
|
||
export const getAllCockpitActionParametersInfo = (): Record<string, CockpitActionParameter> => { | ||
return cockpitActionParametersInfo | ||
} | ||
|
||
export const getCockpitActionParameterInfo = (id: string): CockpitActionParameter | undefined => { | ||
return cockpitActionParametersInfo[id] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't these two functions (getCockpitActionParametersInfo and getCockpitActionParameterInfo) duplicated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They definitely are 😆
src/libs/actions/http-request.ts
Outdated
export const getHttpRequestActionCallback = (id: string): HttpRequestActionCallback => { | ||
const action = getHttpRequestActionConfig(id) | ||
if (!action) { | ||
throw new Error(`Action with id ${id} not found.`) | ||
} | ||
|
||
let parsedBody = action.body | ||
const parsedUrlParams = action.urlParams | ||
|
||
const cockpitInputsInBody = action.body.match(/{{\s*([^{}\s]+)\s*}}/g) | ||
if (cockpitInputsInBody) { | ||
for (const input of cockpitInputsInBody) { | ||
const parsedInput = input.replace('{{', '').replace('}}', '').trim() | ||
const inputData = getCockpitActionParameterData(parsedInput) | ||
if (inputData) { | ||
parsedBody = parsedBody.replace(input, inputData.toString()) | ||
} | ||
} | ||
} | ||
|
||
const cockpitInputsInUrlParams = Object.entries(action.urlParams).filter( | ||
([, value]) => typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}') | ||
) | ||
if (cockpitInputsInUrlParams) { | ||
for (const [key, value] of cockpitInputsInUrlParams) { | ||
const parsedInput = value.replace('{{', '').replace('}}', '').trim() | ||
const inputData = getCockpitActionParameterData(parsedInput) | ||
if (inputData) { | ||
parsedUrlParams[key] = inputData.toString() | ||
} | ||
} | ||
} | ||
|
||
const url = new URL(action.url) | ||
|
||
url.search = new URLSearchParams(parsedUrlParams).toString() | ||
|
||
return () => { | ||
fetch(url, { | ||
method: action.method, | ||
headers: action.headers, | ||
body: action.method === HttpRequestMethod.GET ? undefined : parsedBody, | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameters for the body and url variables are registered when the callback is created, but if they change by the time its executed, the new values won't be updated.
Maybe moving them inside the return, like:
export const getHttpRequestActionCallback = (id: string): HttpRequestActionCallback => {
const action = getHttpRequestActionConfig(id)
if (!action) {
throw new Error(`Action with id ${id} not found.`)
}
return () => {
let parsedBody = action.body
const parsedUrlParams = { ...action.urlParams }
[...]
fetch(url, {
method: action.method,
headers: action.headers,
body: action.method === HttpRequestMethod.GET ? undefined : parsedBody,
})
}
}
I think we can add a simple validator to check at least if the json structure is the same. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Architecture and implementation are awesome!
Just some minor changes as commented earlier.
284e1ee
to
a6fa73d
Compare
@ArturoManzoli all UI changes were performed. I've also added file validation for the "Import action" button and changed the data-lake structures to "CockpitActionVariable" (former "CockpitActionParameter"). |
Kapture.2024-10-03.at.14.56.15.mp4
Import can be tested with the following action files:
http-request-action (Arm with HTTP).json
http-request-action (Disarm with HTTP).json
Fix #568