Skip to content

Commit dc3d8bd

Browse files
committed
new changes for source creation and test coverage
1 parent d8b6590 commit dc3d8bd

File tree

2 files changed

+678
-372
lines changed

2 files changed

+678
-372
lines changed

ui/src/components/EventConfigEventCreate/BasicInformation.vue

Lines changed: 151 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
<template>
22
<div class="main-content">
3-
<div class="header">
4-
<div>
5-
<FeatherBackButton
6-
data-test="back-button"
7-
@click="handleCancel(store.selectedSource?.id)"
8-
>
9-
Go Back
10-
</FeatherBackButton>
3+
<div class="title">
4+
<div class="header">
5+
<div>
6+
<FeatherBackButton
7+
data-test="back-button"
8+
@click="handleCancel(store.selectedSource?.id)"
9+
>
10+
Go Back
11+
</FeatherBackButton>
12+
</div>
13+
<div>
14+
<h3>
15+
{{ store.eventModificationState.isEditMode === CreateEditMode.Create ? 'Create New Event Configuration' :
16+
'Edit Event Configuration Details' }}
17+
</h3>
18+
</div>
1119
</div>
12-
<div>
13-
<h3>
14-
{{ store.eventModificationState.isEditMode === CreateEditMode.Create ? 'Create New Event Configuration' :
15-
'Edit Event Configuration Details' }}
16-
</h3>
20+
<div class="action">
21+
<FeatherButton
22+
primary
23+
@click="showSourceCreationDialog"
24+
data-test="create-new-event-source-button"
25+
:disabled="store.selectedSource?.name && store.selectedSource?.id ? true : false"
26+
>
27+
Create New Event Source
28+
</FeatherButton>
1729
</div>
1830
</div>
1931
<div class="spacer"></div>
@@ -22,7 +34,7 @@
2234
<div class="spacer"></div>
2335
<FeatherAutocomplete
2436
class="my-autocomplete"
25-
:disabled="store.selectedSource?.name ? true : false"
37+
:disabled="store.selectedSource?.name && store.selectedSource?.id ? true : false"
2638
:model-value="selectedSource"
2739
@update:model-value="(item: any) => setSelectedSource(item)"
2840
label="Source Name"
@@ -192,6 +204,47 @@
192204
</div>
193205
</div>
194206
</div>
207+
<FeatherDialog
208+
v-model="sourceCreationDialogState"
209+
:labels="labels"
210+
hide-close
211+
@hidden="handleSourceCreationCancel"
212+
>
213+
<div class="modal-body-form">
214+
<div>
215+
<FeatherInput
216+
label="Event Configuration Source Name"
217+
v-model="configName"
218+
:error="sourceCreationErrors?.name"
219+
data-test="source-name"
220+
/>
221+
</div>
222+
<div>
223+
<FeatherInput
224+
label="Vendor"
225+
v-model="vendor"
226+
:error="sourceCreationErrors?.vendor"
227+
data-test="vendor"
228+
/>
229+
</div>
230+
</div>
231+
<template v-slot:footer>
232+
<FeatherButton
233+
@click="handleSourceCreationCancel"
234+
data-test="cancel-source-button"
235+
>
236+
Cancel
237+
</FeatherButton>
238+
<FeatherButton
239+
primary
240+
@click="handleSourceCreationSave"
241+
:disabled="Object.keys(sourceCreationErrors || {}).length > 0"
242+
data-test="create-source-button"
243+
>
244+
Create Source
245+
</FeatherButton>
246+
</template>
247+
</FeatherDialog>
195248
</div>
196249
</template>
197250

@@ -205,6 +258,7 @@ import { EventConfigEvent, EventFormErrors } from '@/types/eventConfig'
205258
import { FeatherAutocomplete, IAutocompleteItemType } from '@featherds/autocomplete'
206259
import { FeatherBackButton } from '@featherds/back-button'
207260
import { FeatherButton } from '@featherds/button'
261+
import { FeatherDialog } from '@featherds/dialog'
208262
import { FeatherIcon } from '@featherds/icon'
209263
import MoreVert from '@featherds/icon/navigation/MoreVert'
210264
import { FeatherInput } from '@featherds/input'
@@ -217,7 +271,6 @@ import { validateEvent } from './eventValidator'
217271
import MaskElements from './MaskElements.vue'
218272
import MaskVarbinds from './MaskVarbinds.vue'
219273
import VarbindsDecode from './VarbindsDecode.vue'
220-
import { mapEventConfigSourceFromServer } from '@/mappers/eventConfig.mapper'
221274
222275
const loading = ref(false)
223276
const timeout = ref<number>(-1)
@@ -238,6 +291,9 @@ const snackbar = useSnackbar()
238291
const destination = ref<ISelectItemType>({ _text: '', _value: '' })
239292
const severity = ref<ISelectItemType>({ _text: '', _value: '' })
240293
const alarmType = ref<ISelectItemType>({ _text: '', _value: '' })
294+
const configName = ref('')
295+
const vendor = ref('')
296+
const sourceCreationDialogState = ref(false)
241297
const maskElements = ref<Array<{ name: ISelectItemType; value: string }>>([
242298
{ name: { _text: '', _value: '' }, value: '' }
243299
])
@@ -249,6 +305,22 @@ const varbinds = ref<Array<{ index: string; value: string, type: ISelectItemType
249305
{ index: '0', value: '', type: { _text: MaskVarbindsTypeText.vbNumber, _value: MaskVarbindsTypeValue.vbNumber } }
250306
])
251307
const varbindsDecode = ref<Array<{ parmId: string; decode: Array<{ key: string; value: string }> }>>([])
308+
const labels = {
309+
title: 'Create New Event Source'
310+
}
311+
const sourceCreationErrors = computed(() => {
312+
let error: any = {}
313+
if (configName.value.trim() === '') {
314+
error.name = 'Configuration name is required.'
315+
}
316+
if (vendor.value.trim() === '') {
317+
error.vendor = 'Vendor is required.'
318+
}
319+
if (vendor.value && vendor.value.length > 128) {
320+
error.vendor = 'Vendor must be less than 128 characters.'
321+
}
322+
return Object.keys(error).length > 0 ? error : null
323+
})
252324
253325
const xmlContent = computed(() => {
254326
return vkbeautify.xml(
@@ -541,27 +613,14 @@ const handleSaveEvent = async () => {
541613
}
542614
543615
if (selectedSource.value?._value === -1) {
544-
snackbar.showSnackBar({ msg: 'Source is required', error: true })
616+
snackbar.showSnackBar({ msg: 'No source selected. Please select a source from the dropdown or create a new one.', error: true })
545617
return
546618
}
547619
548620
try {
549-
if (selectedSource.value?._value === 0) {
550-
const response = await addEventConfigSource(
551-
selectedSource.value?._text as string,
552-
selectedSource.value?._text as string,
553-
''
554-
)
555-
await store.fetchSourceById(String(mapEventConfigSourceFromServer(response).id))
556-
selectedSource.value = {
557-
_text: store.selectedSource?.name || '',
558-
_value: store.selectedSource?.id || -1
559-
}
560-
}
561621
const sourceId = selectedSource.value?._value as number
562-
563622
if (!sourceId) {
564-
snackbar.showSnackBar({ msg: 'Source is required', error: true })
623+
snackbar.showSnackBar({ msg: 'No source selected. Please select a source from the dropdown or create a new one.', error: true })
565624
return
566625
}
567626
@@ -606,6 +665,59 @@ const handleCancel = (id?: number) => {
606665
}
607666
}
608667
668+
const showSourceCreationDialog = () => {
669+
configName.value = ''
670+
vendor.value = ''
671+
sourceCreationDialogState.value = true
672+
}
673+
674+
const handleSourceCreationSave = async () => {
675+
try {
676+
const response = await addEventConfigSource(
677+
configName.value,
678+
vendor.value,
679+
''
680+
)
681+
if (response && typeof response === 'object' && response.status === 201) {
682+
// Success: response contains { id, name, fileOrder, status: 201 }
683+
await eventConfigStore.fetchAllSourcesNames()
684+
selectedSource.value = { _text: response.name, _value: response.id }
685+
configName.value = ''
686+
vendor.value = ''
687+
sourceCreationDialogState.value = false
688+
} else if (response === 409) {
689+
// Conflict: duplicate name
690+
snackbar.showSnackBar({
691+
msg: 'An event configuration source with this name already exists.',
692+
error: true
693+
})
694+
} else if (response === 400) {
695+
// Bad request: validation error
696+
snackbar.showSnackBar({
697+
msg: 'Invalid request. Please check your input and try again.',
698+
error: true
699+
})
700+
} else {
701+
// 500 or any other error
702+
snackbar.showSnackBar({
703+
msg: 'Failed to create event configuration source. Please try again.',
704+
error: true
705+
})
706+
}
707+
} catch (error) {
708+
snackbar.showSnackBar({
709+
msg: 'Failed to create event configuration source. Please try again.',
710+
error: true
711+
})
712+
}
713+
}
714+
715+
const handleSourceCreationCancel = () => {
716+
configName.value = ''
717+
vendor.value = ''
718+
sourceCreationDialogState.value = false
719+
}
720+
609721
watchEffect(() => {
610722
const currentErrors = validateEvent(
611723
eventUei.value,
@@ -639,20 +751,9 @@ const search = (query: string) => {
639751
loading.value = true
640752
clearTimeout(timeout.value)
641753
timeout.value = window.setTimeout(() => {
642-
const list = eventConfigStore.uploadedSources || []
643-
const filtered = list
754+
results.value = eventConfigStore.uploadedSources
644755
.filter((s) => s.name.toLowerCase().includes(query.toLowerCase()))
645756
.map((x) => ({ _text: x.name, _value: x.id }))
646-
647-
// Check if there's an exact match
648-
const hasExactMatch = filtered.some((item) => item._text.toLowerCase() === query.toLowerCase())
649-
650-
// Add fallback option only if no exact match exists and query is not empty
651-
if (!hasExactMatch && query.trim().length > 0) {
652-
results.value = [...filtered, { _text: query.trim(), _value: 0 }]
653-
} else {
654-
results.value = filtered
655-
}
656757
loading.value = false
657758
}, 500)
658759
}
@@ -674,10 +775,16 @@ onMounted(async () => {
674775
border-radius: 8px;
675776
background-color: #ffffff;
676777
677-
.header {
778+
.title {
678779
display: flex;
679780
align-items: center;
680-
gap: 20px;
781+
justify-content: space-between;
782+
783+
.header {
784+
display: flex;
785+
align-items: center;
786+
gap: 20px;
787+
}
681788
}
682789
683790
.basic-info {

0 commit comments

Comments
 (0)