Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
14 changes: 14 additions & 0 deletions src/api/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,17 @@ export function debugContent(pipeline: string, data: any, contentType: string) {
}
)
}

export function getPipelineDDL(pipelineName: string, tableName: string) {
const appStore = useAppStore()
return axios
.get(`/v1/pipelines/${pipelineName}/ddl`, {
params: {
table: tableName,
db: appStore.database,
},
})
.then((result) => {
return result.sql.sql
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing nested property sql.sql without null/undefined checks could cause runtime errors if the API response structure is unexpected. Consider using optional chaining: return result.sql?.sql

Suggested change
return result.sql.sql
return result.sql?.sql

Copilot uses AI. Check for mistakes.

})
}
16 changes: 14 additions & 2 deletions src/components/yml-editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,37 @@ a-card.light-editor-card(:bordered="false")
</template>

<script lang="ts" setup name="YMLEditorSimple">
import { computed } from 'vue'
import { Codemirror as CodeMirror } from 'vue-codemirror'
import { basicSetup } from 'codemirror'

import * as yamlMode from '@codemirror/legacy-modes/mode/yaml'
import { StreamLanguage, LanguageSupport } from '@codemirror/language'
import { sql } from '@codemirror/lang-sql'

const props = defineProps<{
modelValue: string
disabled?: boolean
placeholder?: string
language?: string
}>()
const emit = defineEmits<{
(event: 'update:modelValue', value: string): void
}>()

const yaml = new LanguageSupport(StreamLanguage.define(yamlMode.yaml))

// TODO: markdown extension
const extensions = [basicSetup, yaml]
const extensions = computed(() => {
const baseExtensions = [basicSetup]

if (props.language === 'sql') {
baseExtensions.push(sql())
} else {
baseExtensions.push(yaml)
}

return baseExtensions
})
const codeUpdate = (content) => {
emit('update:modelValue', content)
}
Expand Down
44 changes: 36 additions & 8 deletions src/views/dashboard/logs/pipelines/PipeFileView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ a-layout.full-height-layout.pipefile-view(
| Delete
a-button(type="primary" size="small" @click="handleSave")
| Save
a-card.pipeline-actions-card(v-if="!isCreating")
a-button(type="text" size="small" @click="handleIngest")
| Ingest With Pipeline
a-button(type="text" size="small" @click="showCreateTableModal")
| Create Table from Pipeline

a-form(
ref="formRef"
layout="vertical"
Expand All @@ -39,13 +45,7 @@ a-layout.full-height-layout.pipefile-view(
a-form-item(v-if="!isCreating" field="version" label="Version")
a-space
| {{ currFile.version }}
a-button(
v-if="!isCreating"
type="text"
size="small"
@click="handleIngest"
)
| Ingest With Pipeline

a-form-item(field="content" label="Yaml Content")
template(#help)
div
Expand Down Expand Up @@ -143,6 +143,9 @@ a-layout.full-height-layout.pipefile-view(
:tabSize="2"
:disabled="true"
)

// Create Table Modal
CreateTableModal(ref="createTableModalRef" :pipeline-name="currFile.name" @tableCreated="() => {}")
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @tableCreated event handler is an empty function. Consider either removing the event handler if no action is needed, or implement proper handling such as refreshing data or showing a success message.

Copilot uses AI. Check for mistakes.

</template>

<script setup name="PipeFileView" lang="ts">
Expand All @@ -155,6 +158,8 @@ a-layout.full-height-layout.pipefile-view(
import type { ColumnType } from '@/types/query'
import router from '@/router'
import DataTable from '@/components/data-table/index.vue'
import YMLEditorSimple from '@/components/yml-editor.vue'
import CreateTableModal from './create-table-modal/index.vue'
import { toObj } from '../query/until'

const emit = defineEmits(['refresh', 'del'])
Expand Down Expand Up @@ -366,6 +371,8 @@ transform:

const ymlError = ref('')

const createTableModalRef = ref()

function getData() {
const name = props.filename
if (name) {
Expand All @@ -384,6 +391,13 @@ transform:
},
})
}

// Show create table modal
const showCreateTableModal = () => {
if (createTableModalRef.value?.open) {
createTableModalRef.value.open()
}
}
</script>

<style lang="less" scoped>
Expand All @@ -401,7 +415,7 @@ transform:
height: calc(100vh - 238px); // Taller for creating mode (no version field)

&.editing {
height: calc(100vh - 238px); // Shorter for editing mode (has version field)
height: calc(100vh - 298px); // Shorter for editing mode (has version field)
}
}

Expand Down Expand Up @@ -588,4 +602,18 @@ transform:
margin: 10px 0 0;
position: relative;
}

// ===================
// PIPELINE ACTIONS CARD
// ===================
.pipeline-actions-card {
border: 1px solid var(--color-border-2);
border-radius: 4px;
margin: 10px 10px 0 10px;
padding: 10px 2px;

:deep(.arco-card-body) {
padding: 0;
}
}
</style>
133 changes: 133 additions & 0 deletions src/views/dashboard/logs/pipelines/create-table-modal/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<template lang="pug">
a-modal(
v-model:visible="visible"
title="Create Pipeline Ingestion Table"
:width="700"
@ok="handleCreate"
@cancel="visible = false"
)
template(#footer)
a-space
a-button(@click="visible = false") Cancel
a-button(type="primary" :loading="creating" @click="handleCreate") Create Table

.create-table-content
a-space(
style="background-color: var(--color-fill-2); padding: 16px; border-radius: 4px; width: 100%; margin-bottom: 16px"
)
span.label Table Name
a-input(v-model="tableName" placeholder="Enter table name" style="width: 200px; margin-right: 8px")
a-button(type="outline" :loading="loadingDDL" @click="handleGetDDL") Get CREATE TABLE SQL from Pipeline

.sql-content-section
a-form(
ref="formRef"
layout="vertical"
:model="formData"
:rules="rules"
@submit="handleCreate"
)
a-form-item(field="createTableSQL" label="CREATE TABLE SQL" required)
YMLEditorSimple(
v-model="formData.createTableSQL"
language="sql"
style="width: 100%; height: 300px; border: 1px solid var(--color-border); border-radius: 4px; overflow: hidden"
placeholder="CREATE TABLE SQL"
)
</template>

<script setup lang="ts">
import { ref, reactive, computed, defineExpose } from 'vue'
import { Notification } from '@arco-design/web-vue'
import YMLEditorSimple from '@/components/yml-editor.vue'
import editorAPI from '@/api/editor'
import { getPipelineDDL } from '@/api/pipeline'
const props = defineProps<{ pipelineName: string }>()
const emit = defineEmits<{ (e: 'tableCreated'): void }>()
const visible = ref(false)
const creating = ref(false)
const loadingDDL = ref(false)
const tableName = ref('')
const formRef = ref()
const formData = reactive({
createTableSQL: '',
})
const rules = {
createTableSQL: [
{
required: true,
message: 'Please enter table creation SQL',
},
],
}
function open() {
tableName.value = ''
formData.createTableSQL = ''
visible.value = true
}
defineExpose({ open })
const handleGetDDL = async () => {
if (!tableName.value.trim()) {
Notification.warning('Please enter a table name')
return
}
if (!props.pipelineName) {
Notification.warning('Pipeline name is required to get DDL')
return
}
try {
loadingDDL.value = true
formData.createTableSQL = await getPipelineDDL(props.pipelineName, tableName.value)
} finally {
loadingDDL.value = false
}
}
const handleCreate = async () => {
if (!formRef.value) return
try {
const valid = await formRef.value.validate()
if (valid !== undefined) {
// Validation failed, valid contains the error details
Copy link
Preview

Copilot AI Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment mentions that valid contains error details, but these errors are not being handled or displayed to the user. Consider logging the validation errors or showing them in the UI.

Suggested change
// Display validation errors to the user
const errorMessages = Array.isArray(valid)
? valid.map(err => err.message).join('\n')
: typeof valid === 'object' && valid !== null
? Object.values(valid).map((errs: any) =>
Array.isArray(errs)
? errs.map((e: any) => e.message).join('\n')
: typeof errs === 'object' && errs.message
? errs.message
: ''
).join('\n')
: String(valid)
Notification.warning({
title: 'Validation Error',
content: errorMessages || 'Form validation failed. Please check your input.',
})

Copilot uses AI. Check for mistakes.

return
}
creating.value = true
await editorAPI.runSQL(formData.createTableSQL)
emit('tableCreated')
visible.value = false
tableName.value = ''
formData.createTableSQL = ''
Notification.success('SQL executed successfully')
} finally {
creating.value = false
}
}
</script>

<style lang="less" scoped>
.create-table-content {
p {
margin-bottom: 16px;
color: var(--color-text-2);
}
}
:deep(.arco-form-item-label) {
font-weight: 500;
}
:deep(.arco-form-item-help) {
margin-top: 4px;
}
</style>
2 changes: 0 additions & 2 deletions src/views/dashboard/logs/pipelines/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ a-spin(style="width: 100%; height: 100%")
import type { PipeFile } from '@/api/pipeline'
import PipeFileView from './PipeFileView.vue'

const { dataStatusMap } = storeToRefs(useUserStore())

const route = useRoute()
const { filename } = route.query
const selectedKeys = ref<Array<string>>(filename ? [filename as string] : [])
Expand Down