Skip to content

Commit b22430e

Browse files
authored
feat(ingest): support log ingestion with pipeline (#521)
* feat: init ingest * refactor(ingest): components rewrite * refactor: rename * feat: remove components * feat(ingest): log ingestion with pipeline * style: new icon for log ingestion * feat(ingest): select format for log ingestion * docs: format * style: sidebar width * feat(ingest): responsive codemirror format and placeholder
1 parent db4a0cf commit b22430e

15 files changed

Lines changed: 382 additions & 29 deletions

File tree

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"Greptime",
88
"hoverable",
99
"Keymap",
10+
"ndjson",
1011
"pinia",
1112
"promql",
1213
"resizebox",

src/api/pipeline.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import editorAPI from './editor'
66
const { runSQL } = editorAPI
77
const url = '/v1/events/pipelines'
88
const sqlUrl = `/v1/sql`
9+
const pipelineLogsUrl = '/v1/events/logs'
910

1011
const makeSqlData = (sql: string) => {
1112
return qs.stringify({
@@ -58,6 +59,25 @@ export function list() {
5859
})
5960
}
6061

62+
export const postPipelineLogs = (
63+
db: string,
64+
table: string,
65+
pipelineName: string,
66+
data: string,
67+
contentType = 'application/x-ndjson'
68+
) => {
69+
return axios.post(pipelineLogsUrl, data, {
70+
params: {
71+
db,
72+
table,
73+
pipeline_name: pipelineName,
74+
},
75+
headers: {
76+
'Content-Type': contentType,
77+
},
78+
})
79+
}
80+
6181
export function getByName(name: string): Promise<PipeFile> {
6282
const sql = `select name, created_at, pipeline
6383
from greptime_private.pipelines

src/assets/icons.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hooks/ingest.ts

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,24 @@ export interface IngestConfig {
1414
export default function useIngest() {
1515
const codeRunStore = useCodeRunStore()
1616

17+
const getPlaceholderByContentType = (contentType: string): string => {
18+
if (contentType === 'application/json') {
19+
return `[
20+
{"message": "hello world", "time": "2024-07-12T16:18:53.048"},
21+
{"message": "hello greptime", "time": "2024-07-12T16:18:53.048"}
22+
]`
23+
}
24+
25+
if (contentType === 'application/x-ndjson') {
26+
return `{"message": "hello world", "time": "2024-07-12T16:18:53.048"}
27+
{"message": "hello greptime", "time": "2024-07-12T16:18:53.048"}`
28+
}
29+
30+
// text/plain
31+
return `hello world 2024-07-12T16:18:53.048
32+
hello greptime 2024-07-12T16:18:53.048`
33+
}
34+
1735
const getInfluxdbInputConfig = (precision: Ref<string>): IngestConfig => ({
1836
type: 'influxdb',
1937
tabKey: 'influxdb-input',
@@ -29,19 +47,6 @@ export default function useIngest() {
2947
},
3048
})
3149

32-
const getLogIngestionInputConfig = (pipeline: Ref<string>): IngestConfig => ({
33-
type: 'log-ingestion',
34-
tabKey: 'log-ingestion-input',
35-
submitLabel: 'Process',
36-
placeholder: 'Enter your log data here...',
37-
hasDoc: false,
38-
successMessage: 'Log processed successfully',
39-
submitHandler: async (content: string) => {},
40-
get params() {
41-
return { pipeline: pipeline.value }
42-
},
43-
})
44-
4550
const getInfluxdbUploadConfig = (precision: Ref<string>): IngestConfig => ({
4651
type: 'influxdb',
4752
tabKey: 'influxdb-upload',
@@ -56,15 +61,45 @@ export default function useIngest() {
5661
},
5762
})
5863

59-
const getLogIngestionUploadConfig = (pipeline: Ref<string>): IngestConfig => ({
64+
const getLogIngestionInputConfig = (
65+
pipeline: Ref<string>,
66+
table: Ref<string>,
67+
contentType: Ref<string>
68+
): IngestConfig => ({
69+
type: 'log-ingestion',
70+
tabKey: 'log-ingestion-input',
71+
submitLabel: 'Write',
72+
get placeholder() {
73+
return getPlaceholderByContentType(contentType.value)
74+
},
75+
hasDoc: false,
76+
successMessage: 'Data written successfully',
77+
submitHandler: async (content: string) => {
78+
return codeRunStore.processLogs(content, table.value, pipeline.value, contentType.value)
79+
},
80+
get params() {
81+
return { pipeline: pipeline.value, table: table.value, contentType: contentType.value }
82+
},
83+
})
84+
85+
const getLogIngestionUploadConfig = (
86+
pipeline: Ref<string>,
87+
table: Ref<string>,
88+
contentType: Ref<string>
89+
): IngestConfig => ({
6090
type: 'log-ingestion',
6191
tabKey: 'log-ingestion-upload',
62-
submitLabel: 'Process',
92+
submitLabel: 'Write',
93+
get placeholder() {
94+
return getPlaceholderByContentType(contentType.value)
95+
},
6396
hasDoc: false,
64-
successMessage: 'Log processed successfully',
65-
submitHandler: async (content: string) => {},
97+
successMessage: 'Data written successfully',
98+
submitHandler: async (content: string) => {
99+
return codeRunStore.processLogs(content, table.value, pipeline.value, contentType.value)
100+
},
66101
get params() {
67-
return { pipeline: pipeline.value }
102+
return { pipeline: pipeline.value, table: table.value, contentType: contentType.value }
68103
},
69104
})
70105

src/router/routes/modules/dashboard.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,42 @@ const DASHBOARD: AppRouteRecordRaw = {
8787
},
8888
],
8989
},
90+
{
91+
path: 'log-ingestion',
92+
name: 'log-ingestion',
93+
redirect: '/dashboard/ingest/log-ingestion/input',
94+
component: () => import('@/views/dashboard/ingest/log-ingestion/index.vue'),
95+
meta: {
96+
locale: 'menu.dashboard.log-ingestion',
97+
requiresAuth: false,
98+
icon: 'upload-logs',
99+
roles: ['admin', 'cloud'],
100+
},
101+
children: [
102+
{
103+
path: 'input',
104+
name: 'log-ingestion-input',
105+
component: () => import('@/views/dashboard/ingest/log-ingestion/input-log-ingestion.vue'),
106+
meta: {
107+
locale: 'menu.dashboard.input',
108+
requiresAuth: false,
109+
roles: ['admin', 'cloud'],
110+
icon: 'input',
111+
},
112+
},
113+
{
114+
path: 'upload',
115+
name: 'log-ingestion-upload',
116+
component: () => import('@/views/dashboard/ingest/log-ingestion/upload-log-ingestion.vue'),
117+
meta: {
118+
locale: 'menu.dashboard.upload',
119+
requiresAuth: false,
120+
roles: ['admin', 'cloud'],
121+
icon: 'upload',
122+
},
123+
},
124+
],
125+
},
90126
],
91127
},
92128
{

src/store/modules/code-run/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { defineStore } from 'pinia'
33
import Message from '@arco-design/web-vue/es/message'
44
import i18n from '@/locale'
55
import editorAPI from '@/api/editor'
6+
import { postPipelineLogs } from '@/api/pipeline'
67
import dayjs from 'dayjs'
78
import { dateTypes } from '@/views/dashboard/config'
89
import { AnyObject } from '@/types/global'
@@ -205,14 +206,24 @@ const useCodeRunStore = defineStore('codeRun', () => {
205206
}
206207
}
207208

209+
const processLogs = async (data: string, table: string, pipeline: string, contentType: string) => {
210+
try {
211+
const appStore = useAppStore()
212+
const res = await postPipelineLogs(appStore.database, table, pipeline, data, contentType)
213+
214+
return res
215+
} catch (error: any) {
216+
return error
217+
}
218+
}
219+
208220
const runWithFormat = async (code: string, queryType: string, promForm?: PromForm, format?: string) => {
209221
const res: any =
210222
queryType === 'sql'
211223
? await editorAPI.runSQLWithCSV(code, format)
212224
: await editorAPI.runPromQL(code, promForm, format)
213225
return res
214226
}
215-
216227
return {
217228
results,
218229
runCode,
@@ -223,6 +234,7 @@ const useCodeRunStore = defineStore('codeRun', () => {
223234
explainResultKeyCount,
224235
explainResult,
225236
runWithFormat,
237+
processLogs,
226238
}
227239
})
228240
export default useCodeRunStore

src/store/modules/ingest/index.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,52 @@
1-
import { ref } from 'vue'
2-
import { defineStore } from 'pinia'
1+
import { list as listPipelines } from '@/api/pipeline'
32

43
const useIngestStore = defineStore('ingest', () => {
54
const activeTab = ref('influxdb-input')
65
const precision = ref('ns')
6+
const pipelineName = ref('')
7+
const tableForPipeline = ref('')
8+
const contentType = ref('application/x-ndjson')
9+
const pipelineList = ref([])
10+
const pipelineLoading = ref(false)
11+
712
const footer = ref<{ [key: string]: boolean }>({
813
'influxdb-input': true,
914
'influxdb-upload': true,
1015
'log-ingestion-input': true,
1116
'log-ingestion-upload': true,
1217
})
1318

19+
const pipelineOptions = computed(() => {
20+
return pipelineList.value.map((pipeline) => ({
21+
label: pipeline.name,
22+
value: pipeline.name,
23+
}))
24+
})
25+
26+
const fetchPipelines = async () => {
27+
pipelineLoading.value = true
28+
try {
29+
pipelineList.value = await listPipelines()
30+
} catch (error) {
31+
console.error('Failed to fetch pipelines:', error)
32+
pipelineList.value = []
33+
} finally {
34+
pipelineLoading.value = false
35+
}
36+
}
37+
1438
return {
1539
activeTab,
1640
precision,
1741
footer,
42+
pipelineName,
43+
tableForPipeline,
44+
contentType,
45+
pipelineList,
46+
pipelineLoading,
47+
pipelineOptions,
48+
fetchPipelines,
1849
}
1950
})
51+
2052
export default useIngestStore

src/views/dashboard/ingest/components/base-input.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ a-drawer.ingest(
4242
<script lang="ts" setup>
4343
import { Codemirror as CodeMirror } from 'vue-codemirror'
4444
import { basicSetup } from 'codemirror'
45+
import { json } from '@codemirror/lang-json'
4546
import type { Log } from '@/store/modules/log/types'
4647
4748
const props = defineProps({
@@ -59,7 +60,16 @@ a-drawer.ingest(
5960
const content = ref('')
6061
const docVisible = ref(false)
6162
const style = { height: '100%' }
62-
const extensions = [basicSetup]
63+
64+
const extensions = computed(() => {
65+
const contentType = props.config?.params?.contentType
66+
67+
if (contentType === 'application/json' || contentType === 'application/x-ndjson') {
68+
return [basicSetup, json()]
69+
}
70+
71+
return [basicSetup]
72+
})
6373
6474
const toggleDoc = () => {
6575
docVisible.value = !docVisible.value

src/views/dashboard/ingest/components/base-upload.vue

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,15 @@ a-layout-content.main-content
6666

6767
a-spin(style="width: 100%" :tip="config.readingTip || 'Reading file...'" :loading="isReadingFile")
6868
a-card.file-scrollbar(:bordered="false")
69-
CodeMirror(v-if="dataFromFile" v-model="codeInEditor" :disabled="true")
69+
CodeMirror(
70+
v-model="codeInEditor"
71+
:style="{ height: '100%' }"
72+
:extensions="extensions"
73+
:spellcheck="true"
74+
:indent-with-tab="true"
75+
:tabSize="2"
76+
:disabled="true"
77+
)
7078
span.load(v-if="collapsed && remainingLines")
7179
a.text ...{{ remainingLines }} lines more
7280
a.button(type="text" size="mini" @click="loadMore") {{ config.expandText || 'Expand' }}
@@ -85,6 +93,7 @@ a-layout-content.main-content
8593
<script lang="ts" setup>
8694
import { Codemirror as CodeMirror } from 'vue-codemirror'
8795
import { basicSetup } from 'codemirror'
96+
import { json } from '@codemirror/lang-json' // 导入JSON语法支持
8897
import type { Log } from '@/store/modules/log/types'
8998
import { isObject } from '@/utils/is'
9099
@@ -218,6 +227,16 @@ a-layout-content.main-content
218227
footer.value[activeTab.value] = false
219228
isProcessLoading.value = false
220229
}
230+
231+
const extensions = computed(() => {
232+
const contentType = props.config?.params?.contentType
233+
234+
if (contentType === 'application/json' || contentType === 'application/x-ndjson') {
235+
return [basicSetup, json()]
236+
}
237+
238+
return [basicSetup]
239+
})
221240
</script>
222241

223242
<style lang="less" scoped>

src/views/dashboard/ingest/components/top-bar-ingest.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ a-space.top-bar
4747
padding-right: 20px;
4848
height: 58px;
4949
background: var(--card-bg-color);
50+
width: 100%;
51+
5052
:deep(.arco-select-view-value) {
5153
font-size: 13px;
5254
color: var(--main-font-color);

0 commit comments

Comments
 (0)