@budibase/frontend-core 3.38.4 → 3.39.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/api/agentTests.ts +55 -0
- package/src/api/agents.ts +18 -0
- package/src/api/automations.ts +15 -0
- package/src/api/chatApps.ts +16 -0
- package/src/api/index.ts +2 -0
- package/src/api/types.ts +2 -0
- package/src/components/Chatbox/ChatConversationPanel.svelte +6 -0
- package/src/components/Chatbox/index.svelte +140 -11
- package/src/components/CoreFilterBuilder.svelte +85 -78
- package/src/components/FilterField.svelte +24 -2
- package/src/components/grid/layout/Grid.svelte +2 -0
- package/src/components/grid/stores/conditions.test.ts +26 -0
- package/src/components/grid/stores/conditions.ts +16 -2
- package/src/components/grid/stores/config.ts +2 -3
- package/src/components/grid/stores/index.ts +1 -0
- package/src/fetch/QueryFetch.ts +14 -6
- package/src/components/ConditionField.svelte +0 -175
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.39.15",
|
|
4
4
|
"description": "Budibase frontend core libraries used in builder and client",
|
|
5
5
|
"author": "Budibase",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"socket.io-client": "^4.7.5"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"vitest": "^
|
|
24
|
+
"vitest": "^4.1.0"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "e409b00d4727dd9f3c5ba22401aa3d0d8c062cba"
|
|
27
27
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FetchAgentTestSuiteResponse,
|
|
3
|
+
FetchAgentTestRunResponse,
|
|
4
|
+
RunAgentTestSuiteRequest,
|
|
5
|
+
RunAgentTestSuiteResponse,
|
|
6
|
+
UpdateAgentTestSuiteRequest,
|
|
7
|
+
UpdateAgentTestSuiteResponse,
|
|
8
|
+
} from "@budibase/types"
|
|
9
|
+
import type { BaseAPIClient } from "./types"
|
|
10
|
+
|
|
11
|
+
export interface AgentTestEndpoints {
|
|
12
|
+
fetchAgentTestSuite: (agentId: string) => Promise<FetchAgentTestSuiteResponse>
|
|
13
|
+
updateAgentTestSuite: (
|
|
14
|
+
agentId: string,
|
|
15
|
+
body: UpdateAgentTestSuiteRequest
|
|
16
|
+
) => Promise<UpdateAgentTestSuiteResponse>
|
|
17
|
+
runAgentTestSuite: (
|
|
18
|
+
agentId: string,
|
|
19
|
+
body?: RunAgentTestSuiteRequest
|
|
20
|
+
) => Promise<RunAgentTestSuiteResponse>
|
|
21
|
+
fetchAgentTestRun: (
|
|
22
|
+
agentId: string,
|
|
23
|
+
runId: string
|
|
24
|
+
) => Promise<FetchAgentTestRunResponse>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const buildAgentTestEndpoints = (
|
|
28
|
+
API: BaseAPIClient
|
|
29
|
+
): AgentTestEndpoints => ({
|
|
30
|
+
fetchAgentTestSuite: async agentId => {
|
|
31
|
+
return await API.get({
|
|
32
|
+
url: `/api/agent/${agentId}/tests`,
|
|
33
|
+
})
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
updateAgentTestSuite: async (agentId, body) => {
|
|
37
|
+
return await API.put({
|
|
38
|
+
url: `/api/agent/${agentId}/tests`,
|
|
39
|
+
body,
|
|
40
|
+
})
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
runAgentTestSuite: async (agentId, body) => {
|
|
44
|
+
return await API.post({
|
|
45
|
+
url: `/api/agent/${agentId}/tests/run`,
|
|
46
|
+
body: body ?? {},
|
|
47
|
+
})
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
fetchAgentTestRun: async (agentId, runId) => {
|
|
51
|
+
return await API.get({
|
|
52
|
+
url: `/api/agent/${agentId}/tests/run/${runId}`,
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
})
|
package/src/api/agents.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
DisconnectAgentSharePointSiteResponse,
|
|
8
8
|
DuplicateAgentResponse,
|
|
9
9
|
FetchAgentKnowledgeResponse,
|
|
10
|
+
FetchAgentFileUrlResponse,
|
|
10
11
|
FetchAgentKnowledgeSourceEntriesResponse,
|
|
11
12
|
FetchAgentKnowledgeSourceOptionsResponse,
|
|
12
13
|
FetchAgentsResponse,
|
|
@@ -79,6 +80,10 @@ export interface AgentEndpoints {
|
|
|
79
80
|
agentId: string,
|
|
80
81
|
fileId: string
|
|
81
82
|
) => Promise<{ deleted: true }>
|
|
83
|
+
fetchAgentFileUrl: (
|
|
84
|
+
agentId: string,
|
|
85
|
+
fileId: string
|
|
86
|
+
) => Promise<FetchAgentFileUrlResponse>
|
|
82
87
|
fetchAgentKnowledgeSourceOptions: (
|
|
83
88
|
datasourceId: string,
|
|
84
89
|
authConfigId: string
|
|
@@ -104,6 +109,7 @@ export interface AgentEndpoints {
|
|
|
104
109
|
agentId: string,
|
|
105
110
|
sourceId: string
|
|
106
111
|
) => Promise<SyncAgentKnowledgeSourcesResponse>
|
|
112
|
+
resetAgentKnowledgeBaseStore: (agentId: string) => Promise<void>
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
|
|
@@ -249,6 +255,12 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
|
|
|
249
255
|
})
|
|
250
256
|
},
|
|
251
257
|
|
|
258
|
+
fetchAgentFileUrl: async (agentId: string, fileId: string) => {
|
|
259
|
+
return await API.get<FetchAgentFileUrlResponse>({
|
|
260
|
+
url: `/api/agent/${agentId}/files/${fileId}/url`,
|
|
261
|
+
})
|
|
262
|
+
},
|
|
263
|
+
|
|
252
264
|
fetchAgentKnowledgeSourceOptions: async (
|
|
253
265
|
datasourceId: string,
|
|
254
266
|
authConfigId: string
|
|
@@ -302,4 +314,10 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
|
|
|
302
314
|
url: `/api/agent/${agentId}/knowledge-sources/${encodeURIComponent(sourceId)}/sync`,
|
|
303
315
|
})
|
|
304
316
|
},
|
|
317
|
+
|
|
318
|
+
resetAgentKnowledgeBaseStore: async (agentId: string) => {
|
|
319
|
+
await API.post({
|
|
320
|
+
url: `/api/agent/${agentId}/knowledge/store/reset`,
|
|
321
|
+
})
|
|
322
|
+
},
|
|
305
323
|
})
|
package/src/api/automations.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
SearchAutomationLogsResponse,
|
|
11
11
|
TestAutomationRequest,
|
|
12
12
|
TestAutomationResponse,
|
|
13
|
+
TestEmailConnectionRequest,
|
|
14
|
+
TestEmailConnectionResponse,
|
|
13
15
|
TestProgressState,
|
|
14
16
|
TriggerAutomationRequest,
|
|
15
17
|
TriggerAutomationResponse,
|
|
@@ -44,6 +46,9 @@ export interface AutomationEndpoints {
|
|
|
44
46
|
data: TestAutomationRequest,
|
|
45
47
|
options?: { async?: boolean }
|
|
46
48
|
) => Promise<TestAutomationResponse>
|
|
49
|
+
testEmailConnection: (
|
|
50
|
+
data: TestEmailConnectionRequest
|
|
51
|
+
) => Promise<TestEmailConnectionResponse>
|
|
47
52
|
getAutomationTestStatus: (automationId: string) => Promise<TestProgressState>
|
|
48
53
|
getAutomationDefinitions: () => Promise<GetAutomationStepDefinitionsResponse>
|
|
49
54
|
getAutomationLogs: (
|
|
@@ -91,6 +96,16 @@ export const buildAutomationEndpoints = (
|
|
|
91
96
|
})
|
|
92
97
|
},
|
|
93
98
|
|
|
99
|
+
testEmailConnection: async data => {
|
|
100
|
+
return await API.post<
|
|
101
|
+
TestEmailConnectionRequest,
|
|
102
|
+
TestEmailConnectionResponse
|
|
103
|
+
>({
|
|
104
|
+
url: "/api/automations/email/test-connection",
|
|
105
|
+
body: data,
|
|
106
|
+
})
|
|
107
|
+
},
|
|
108
|
+
|
|
94
109
|
/**
|
|
95
110
|
* Gets a list of all automations.
|
|
96
111
|
*/
|
package/src/api/chatApps.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
FetchPublishedChatAppsResponse,
|
|
11
11
|
UpdateChatAppRequest,
|
|
12
12
|
AgentMessageMetadata,
|
|
13
|
+
FetchAgentFileUrlResponse,
|
|
13
14
|
} from "@budibase/types"
|
|
14
15
|
import { Header } from "@budibase/shared-core"
|
|
15
16
|
import { BaseAPIClient } from "./types"
|
|
@@ -38,6 +39,11 @@ export interface ChatAppEndpoints {
|
|
|
38
39
|
) => Promise<FetchAgentHistoryResponse>
|
|
39
40
|
fetchChatApp: (workspaceId?: string) => Promise<ChatApp | null>
|
|
40
41
|
setChatAppAgent: (chatAppId: string, agentId: string) => Promise<ChatAppAgent>
|
|
42
|
+
fetchChatAppAgentFileUrl: (
|
|
43
|
+
chatAppId: string,
|
|
44
|
+
agentId: string,
|
|
45
|
+
fileId: string
|
|
46
|
+
) => Promise<FetchAgentFileUrlResponse>
|
|
41
47
|
createChatConversation: (
|
|
42
48
|
chat: CreateChatConversationRequest,
|
|
43
49
|
workspaceId?: string
|
|
@@ -181,6 +187,16 @@ export const buildChatAppEndpoints = (
|
|
|
181
187
|
})
|
|
182
188
|
},
|
|
183
189
|
|
|
190
|
+
fetchChatAppAgentFileUrl: async (
|
|
191
|
+
chatAppId: string,
|
|
192
|
+
agentId: string,
|
|
193
|
+
fileId: string
|
|
194
|
+
) => {
|
|
195
|
+
return await API.get<FetchAgentFileUrlResponse>({
|
|
196
|
+
url: `/api/chatapps/${chatAppId}/agents/${agentId}/files/${fileId}/url`,
|
|
197
|
+
})
|
|
198
|
+
},
|
|
199
|
+
|
|
184
200
|
createChatConversation: async (
|
|
185
201
|
chat: CreateChatConversationRequest,
|
|
186
202
|
workspaceId?: string
|
package/src/api/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { buildMigrationEndpoints } from "./migrations"
|
|
|
47
47
|
import { buildRowActionEndpoints } from "./rowActions"
|
|
48
48
|
import { buildOAuth2Endpoints } from "./oauth2"
|
|
49
49
|
import { buildAgentEndpoints } from "./agents"
|
|
50
|
+
import { buildAgentTestEndpoints } from "./agentTests"
|
|
50
51
|
import { buildAgentLogEndpoints } from "./agentLogs"
|
|
51
52
|
import { buildChatAppEndpoints } from "./chatApps"
|
|
52
53
|
import { buildFeatureFlagEndpoints } from "./features"
|
|
@@ -317,6 +318,7 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
|
|
317
318
|
...buildLogsEndpoints(API),
|
|
318
319
|
...buildMigrationEndpoints(API),
|
|
319
320
|
...buildAgentEndpoints(API),
|
|
321
|
+
...buildAgentTestEndpoints(API),
|
|
320
322
|
...buildAgentLogEndpoints(API),
|
|
321
323
|
...buildChatAppEndpoints(API),
|
|
322
324
|
...buildFeatureFlagEndpoints(API),
|
package/src/api/types.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { UserEndpoints } from "./user"
|
|
|
35
35
|
import { ViewEndpoints } from "./views"
|
|
36
36
|
import { ViewV2Endpoints } from "./viewsV2"
|
|
37
37
|
import { AgentEndpoints } from "./agents"
|
|
38
|
+
import { AgentTestEndpoints } from "./agentTests"
|
|
38
39
|
import { AgentLogEndpoints } from "./agentLogs"
|
|
39
40
|
import { ChatAppEndpoints } from "./chatApps"
|
|
40
41
|
import { NavigationEndpoints } from "./navigation"
|
|
@@ -119,6 +120,7 @@ export type APIError = {
|
|
|
119
120
|
export type APIClient = BaseAPIClient &
|
|
120
121
|
AIEndpoints &
|
|
121
122
|
AgentEndpoints &
|
|
123
|
+
AgentTestEndpoints &
|
|
122
124
|
AgentLogEndpoints &
|
|
123
125
|
ChatAppEndpoints &
|
|
124
126
|
AnalyticsEndpoints &
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
name?: string
|
|
19
19
|
icon?: string
|
|
20
20
|
iconColor?: string
|
|
21
|
+
allowKnowledgeSourceDownload?: boolean
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export let selectedAgentId: string | null = null
|
|
@@ -79,6 +80,10 @@
|
|
|
79
80
|
|
|
80
81
|
$: readOnlyReason = getReadOnlyReason(agentAvailability)
|
|
81
82
|
|
|
83
|
+
$: allowKnowledgeSourceDownload =
|
|
84
|
+
enabledAgentList.find(agent => agent.agentId === selectedAgentId)
|
|
85
|
+
?.allowKnowledgeSourceDownload !== false
|
|
86
|
+
|
|
82
87
|
const selectAgent = (agentId: string) => {
|
|
83
88
|
dispatch("agentSelected", { agentId })
|
|
84
89
|
}
|
|
@@ -127,6 +132,7 @@
|
|
|
127
132
|
{workspaceId}
|
|
128
133
|
{conversationStarters}
|
|
129
134
|
{initialPrompt}
|
|
135
|
+
{allowKnowledgeSourceDownload}
|
|
130
136
|
readOnly={Boolean(readOnlyReason)}
|
|
131
137
|
{readOnlyReason}
|
|
132
138
|
onchatsaved={event => dispatch("chatSaved", event.detail)}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
AgentMessageMetadata,
|
|
14
14
|
} from "@budibase/types"
|
|
15
15
|
import { Header } from "@budibase/shared-core"
|
|
16
|
-
import { tick } from "svelte"
|
|
16
|
+
import { tick, untrack } from "svelte"
|
|
17
17
|
import { createAPIClient } from "@budibase/frontend-core"
|
|
18
18
|
import { Chat } from "@ai-sdk/svelte"
|
|
19
19
|
import { formatToolName } from "../../utils/aiTools"
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
isAgentPreviewChat?: boolean
|
|
42
42
|
readOnly?: boolean
|
|
43
43
|
readOnlyReason?: "disabled" | "deleted" | "offline"
|
|
44
|
+
allowKnowledgeSourceDownload?: boolean
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
let {
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
isAgentPreviewChat = false,
|
|
54
55
|
readOnly = false,
|
|
55
56
|
readOnlyReason,
|
|
57
|
+
allowKnowledgeSourceDownload = true,
|
|
56
58
|
}: Props = $props()
|
|
57
59
|
|
|
58
60
|
let API = $state(
|
|
@@ -72,20 +74,64 @@
|
|
|
72
74
|
let chatAreaElement = $state<HTMLDivElement>()
|
|
73
75
|
let textareaElement = $state<HTMLTextAreaElement>()
|
|
74
76
|
let expandedTools = $state<Record<string, boolean>>({})
|
|
77
|
+
let reasoningTextByMessageId = $state<Record<string, string>>({})
|
|
75
78
|
let inputValue = $state("")
|
|
76
79
|
let lastInitialPrompt = $state("")
|
|
77
80
|
let isPreparingResponse = $state(false)
|
|
78
|
-
|
|
79
81
|
const resetPendingResponse = () => {
|
|
80
82
|
isPreparingResponse = false
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
const openRagSource = async (
|
|
86
|
+
source: NonNullable<AgentMessageMetadata["ragSources"]>[number]
|
|
87
|
+
) => {
|
|
88
|
+
if (!allowKnowledgeSourceDownload) {
|
|
89
|
+
notifications.error("Source downloads are disabled for this agent")
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
if (!source.fileId) {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const resolvedUrl =
|
|
98
|
+
!isAgentPreviewChat && chat?.chatAppId && chat?.agentId
|
|
99
|
+
? (
|
|
100
|
+
await API.fetchChatAppAgentFileUrl(
|
|
101
|
+
chat.chatAppId,
|
|
102
|
+
chat.agentId,
|
|
103
|
+
source.fileId
|
|
104
|
+
)
|
|
105
|
+
).url
|
|
106
|
+
: chat?.agentId
|
|
107
|
+
? (await API.fetchAgentFileUrl(chat.agentId, source.fileId)).url
|
|
108
|
+
: undefined
|
|
109
|
+
if (!resolvedUrl) {
|
|
110
|
+
notifications.error("Could not resolve source file URL")
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const link = document.createElement("a")
|
|
115
|
+
link.href = resolvedUrl
|
|
116
|
+
link.download = source.filename || "source.pdf"
|
|
117
|
+
link.target = "_blank"
|
|
118
|
+
link.rel = "noopener noreferrer"
|
|
119
|
+
link.click()
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error("Failed to resolve knowledge source URL", error)
|
|
122
|
+
notifications.error("Failed to download source file")
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
83
126
|
const getReasoningText = (message: UIMessage<AgentMessageMetadata>) =>
|
|
84
127
|
(message.parts ?? [])
|
|
85
128
|
.filter(isReasoningUIPart)
|
|
86
129
|
.map(p => p.text)
|
|
87
130
|
.join("")
|
|
88
131
|
|
|
132
|
+
const getCachedReasoningText = (message: UIMessage<AgentMessageMetadata>) =>
|
|
133
|
+
reasoningTextByMessageId[message.id] || getReasoningText(message)
|
|
134
|
+
|
|
89
135
|
const isReasoningStreaming = (message: UIMessage<AgentMessageMetadata>) =>
|
|
90
136
|
(message.parts ?? []).some(
|
|
91
137
|
part => isReasoningUIPart(part) && part.state === "streaming"
|
|
@@ -108,7 +154,32 @@
|
|
|
108
154
|
return true
|
|
109
155
|
}
|
|
110
156
|
|
|
111
|
-
return Boolean(message.
|
|
157
|
+
return Boolean(getVisibleRagSources(message).length)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const getVisibleRagSources = (message: UIMessage<AgentMessageMetadata>) => {
|
|
161
|
+
const ragSources = message.metadata?.ragSources || []
|
|
162
|
+
const uniqueByFileId = new Set<string>()
|
|
163
|
+
const visible: NonNullable<AgentMessageMetadata["ragSources"]> = []
|
|
164
|
+
|
|
165
|
+
for (const source of ragSources) {
|
|
166
|
+
const filename = source.filename?.trim()
|
|
167
|
+
if (!source.fileId || !filename) {
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (uniqueByFileId.has(source.fileId)) {
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
uniqueByFileId.add(source.fileId)
|
|
176
|
+
visible.push({
|
|
177
|
+
...source,
|
|
178
|
+
filename,
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return visible
|
|
112
183
|
}
|
|
113
184
|
|
|
114
185
|
const hasToolError = (message: UIMessage<AgentMessageMetadata>) =>
|
|
@@ -275,6 +346,36 @@
|
|
|
275
346
|
}
|
|
276
347
|
chatInstance.messages = chat?.messages || []
|
|
277
348
|
expandedTools = {}
|
|
349
|
+
reasoningTextByMessageId = {}
|
|
350
|
+
}
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
$effect(() => {
|
|
354
|
+
const nextReasoningTextByMessageId = untrack(() => ({
|
|
355
|
+
...reasoningTextByMessageId,
|
|
356
|
+
}))
|
|
357
|
+
let hasChanged = false
|
|
358
|
+
|
|
359
|
+
for (const message of messages) {
|
|
360
|
+
if (message.role !== "assistant") {
|
|
361
|
+
continue
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const reasoningText = getReasoningText(message)
|
|
365
|
+
if (!reasoningText) {
|
|
366
|
+
continue
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (nextReasoningTextByMessageId[message.id] === reasoningText) {
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
nextReasoningTextByMessageId[message.id] = reasoningText
|
|
374
|
+
hasChanged = true
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (hasChanged) {
|
|
378
|
+
reasoningTextByMessageId = nextReasoningTextByMessageId
|
|
278
379
|
}
|
|
279
380
|
})
|
|
280
381
|
|
|
@@ -519,7 +620,7 @@
|
|
|
519
620
|
<MarkdownViewer value={getUserMessageText(message)} />
|
|
520
621
|
</div>
|
|
521
622
|
{:else if message.role === "assistant"}
|
|
522
|
-
{@const reasoningText =
|
|
623
|
+
{@const reasoningText = getCachedReasoningText(message)}
|
|
523
624
|
{@const reasoningId = `${message.id}-reasoning`}
|
|
524
625
|
{@const pendingAssistant =
|
|
525
626
|
isBusy &&
|
|
@@ -528,14 +629,22 @@
|
|
|
528
629
|
{@const toolError = hasToolError(message)}
|
|
529
630
|
{@const messageError = getMessageError(message)}
|
|
530
631
|
{@const reasoningStreaming = isReasoningStreaming(message)}
|
|
632
|
+
{@const activeAssistant =
|
|
633
|
+
isBusy && lastAssistantMessage?.id === message.id}
|
|
531
634
|
{@const isThinking =
|
|
532
|
-
(reasoningStreaming || pendingAssistant) &&
|
|
635
|
+
(reasoningStreaming || pendingAssistant || activeAssistant) &&
|
|
533
636
|
!toolError &&
|
|
534
637
|
!messageError &&
|
|
535
638
|
!message.metadata?.completedAt}
|
|
639
|
+
{@const showReasoningStatus =
|
|
640
|
+
reasoningText ||
|
|
641
|
+
pendingAssistant ||
|
|
642
|
+
activeAssistant ||
|
|
643
|
+
message.metadata?.createdAt ||
|
|
644
|
+
message.metadata?.completedAt}
|
|
536
645
|
{#if hasVisibleAssistantContent(message) || pendingAssistant}
|
|
537
646
|
<div class="message assistant">
|
|
538
|
-
{#if
|
|
647
|
+
{#if showReasoningStatus}
|
|
539
648
|
<ReasoningStatus
|
|
540
649
|
thinking={isThinking}
|
|
541
650
|
label={isThinking ? "Thinking" : "Thought"}
|
|
@@ -649,7 +758,7 @@
|
|
|
649
758
|
</div>
|
|
650
759
|
{/if}
|
|
651
760
|
{/each}
|
|
652
|
-
{#if message.
|
|
761
|
+
{#if getVisibleRagSources(message).length}
|
|
653
762
|
<div class="sources">
|
|
654
763
|
<div class="sources-header">
|
|
655
764
|
<span class="sources-icon">
|
|
@@ -662,11 +771,19 @@
|
|
|
662
771
|
<span class="sources-title">Sources</span>
|
|
663
772
|
</div>
|
|
664
773
|
<ul>
|
|
665
|
-
{#each message
|
|
774
|
+
{#each getVisibleRagSources(message) as source (source.fileId)}
|
|
666
775
|
<li class="source-item">
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
776
|
+
{#if allowKnowledgeSourceDownload}
|
|
777
|
+
<button
|
|
778
|
+
type="button"
|
|
779
|
+
class="source-link"
|
|
780
|
+
onclick={() => openRagSource(source)}
|
|
781
|
+
>
|
|
782
|
+
{source.filename}
|
|
783
|
+
</button>
|
|
784
|
+
{:else}
|
|
785
|
+
<span class="source-name">{source.filename}</span>
|
|
786
|
+
{/if}
|
|
670
787
|
</li>
|
|
671
788
|
{/each}
|
|
672
789
|
</ul>
|
|
@@ -1160,4 +1277,16 @@
|
|
|
1160
1277
|
.source-name {
|
|
1161
1278
|
font-weight: 400;
|
|
1162
1279
|
}
|
|
1280
|
+
|
|
1281
|
+
.source-link {
|
|
1282
|
+
border: none;
|
|
1283
|
+
background: transparent;
|
|
1284
|
+
padding: 0;
|
|
1285
|
+
margin: 0;
|
|
1286
|
+
font-weight: 400;
|
|
1287
|
+
color: var(--spectrum-global-color-blue-700);
|
|
1288
|
+
text-decoration: underline;
|
|
1289
|
+
cursor: pointer;
|
|
1290
|
+
text-align: left;
|
|
1291
|
+
}
|
|
1163
1292
|
</style>
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
import { QueryUtils, Constants } from "@budibase/frontend-core"
|
|
17
17
|
import { getContext, createEventDispatcher } from "svelte"
|
|
18
18
|
import FilterField from "./FilterField.svelte"
|
|
19
|
-
import ConditionField from "./ConditionField.svelte"
|
|
20
19
|
import { utils } from "@budibase/shared-core"
|
|
21
20
|
|
|
22
21
|
const dispatch = createEventDispatcher()
|
|
@@ -35,8 +34,12 @@
|
|
|
35
34
|
export let behaviourFilters = false
|
|
36
35
|
export let allowBindings = false
|
|
37
36
|
export let allowOnEmpty = true
|
|
38
|
-
export let builderType = "filter"
|
|
39
37
|
export let docsURL = "https://docs.budibase.com/docs/searchfilter-data"
|
|
38
|
+
export let prefix = "Show data which matches"
|
|
39
|
+
export let filterTypeLabel = "filter"
|
|
40
|
+
export let drawerTitle = null
|
|
41
|
+
export let bindingValueType = Constants.FilterValueType.BINDING
|
|
42
|
+
export let useConditionValueControls = false
|
|
40
43
|
|
|
41
44
|
export let bindings
|
|
42
45
|
export let panel
|
|
@@ -57,10 +60,6 @@
|
|
|
57
60
|
schemaFields = [...schemaFields, { name: "_id", type: "string" }]
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
|
-
$: prefix =
|
|
61
|
-
builderType === "filter"
|
|
62
|
-
? "Show data which matches"
|
|
63
|
-
: "Run branch when matching"
|
|
64
63
|
|
|
65
64
|
// We still may need to migrate this even though the backend does it automatically now
|
|
66
65
|
// for query definitions. This is because we might be editing saved filter definitions
|
|
@@ -111,17 +110,9 @@
|
|
|
111
110
|
}
|
|
112
111
|
|
|
113
112
|
const getValidOperatorsForType = filter => {
|
|
114
|
-
if (builderType === "condition") {
|
|
115
|
-
return [OperatorOptions.Equals, OperatorOptions.NotEquals]
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (!filter?.field && !filter?.name) {
|
|
119
|
-
return []
|
|
120
|
-
}
|
|
121
|
-
|
|
122
113
|
return QueryUtils.getValidOperatorsForType(
|
|
123
|
-
filter,
|
|
124
|
-
filter
|
|
114
|
+
filter?.type ? filter : { ...filter, type: FieldType.STRING },
|
|
115
|
+
filter?.field || filter?.name,
|
|
125
116
|
datasource
|
|
126
117
|
)
|
|
127
118
|
}
|
|
@@ -234,9 +225,6 @@
|
|
|
234
225
|
} else if (addFilter) {
|
|
235
226
|
targetGroup.filters.push({
|
|
236
227
|
valueType: FilterValueType.VALUE,
|
|
237
|
-
...(builderType === "condition"
|
|
238
|
-
? { operator: OperatorOptions.Equals.value, type: FieldType.STRING }
|
|
239
|
-
: {}),
|
|
240
228
|
})
|
|
241
229
|
} else if (group) {
|
|
242
230
|
editable.groups[groupIdx] = {
|
|
@@ -257,11 +245,6 @@
|
|
|
257
245
|
filters: [
|
|
258
246
|
{
|
|
259
247
|
valueType: FilterValueType.VALUE,
|
|
260
|
-
...(builderType === "condition"
|
|
261
|
-
? {
|
|
262
|
-
operator: OperatorOptions.Equals.value,
|
|
263
|
-
}
|
|
264
|
-
: {}),
|
|
265
248
|
},
|
|
266
249
|
],
|
|
267
250
|
})
|
|
@@ -307,7 +290,7 @@
|
|
|
307
290
|
popoverAutoWidth
|
|
308
291
|
/>
|
|
309
292
|
</span>
|
|
310
|
-
<span>of the following {
|
|
293
|
+
<span>of the following {filterTypeLabel} groups:</span>
|
|
311
294
|
</div>
|
|
312
295
|
{/if}
|
|
313
296
|
{#if editableFilters?.groups?.length}
|
|
@@ -337,7 +320,7 @@
|
|
|
337
320
|
popoverAutoWidth
|
|
338
321
|
/>
|
|
339
322
|
</span>
|
|
340
|
-
<span>of the following {
|
|
323
|
+
<span>of the following {filterTypeLabel}s are matched:</span>
|
|
341
324
|
</div>
|
|
342
325
|
<div class="group-actions">
|
|
343
326
|
<Icon
|
|
@@ -367,8 +350,22 @@
|
|
|
367
350
|
|
|
368
351
|
<div class="filters">
|
|
369
352
|
{#each group.filters as filter, filterIdx}
|
|
370
|
-
<div
|
|
371
|
-
|
|
353
|
+
<div
|
|
354
|
+
class="filter"
|
|
355
|
+
class:has-extra-column={!!$$slots["extra-column"]}
|
|
356
|
+
>
|
|
357
|
+
{#if $$slots["field-column"]}
|
|
358
|
+
<slot
|
|
359
|
+
name="field-column"
|
|
360
|
+
{filter}
|
|
361
|
+
{groupIdx}
|
|
362
|
+
{filterIdx}
|
|
363
|
+
onUpdate={f =>
|
|
364
|
+
onFilterFieldUpdate(f, groupIdx, filterIdx)}
|
|
365
|
+
{sanitizeOperator}
|
|
366
|
+
{sanitizeValue}
|
|
367
|
+
/>
|
|
368
|
+
{:else}
|
|
372
369
|
<Select
|
|
373
370
|
value={filter.field}
|
|
374
371
|
options={fieldOptions}
|
|
@@ -380,28 +377,10 @@
|
|
|
380
377
|
placeholder="Column"
|
|
381
378
|
popoverAutoWidth
|
|
382
379
|
/>
|
|
383
|
-
{:else}
|
|
384
|
-
<ConditionField
|
|
385
|
-
placeholder="Value"
|
|
386
|
-
{filter}
|
|
387
|
-
drawerTitle={"Edit Binding"}
|
|
388
|
-
{bindings}
|
|
389
|
-
{panel}
|
|
390
|
-
{toReadable}
|
|
391
|
-
{toRuntime}
|
|
392
|
-
{evaluationContext}
|
|
393
|
-
on:change={e => {
|
|
394
|
-
const updated = {
|
|
395
|
-
...filter,
|
|
396
|
-
field: e.detail.field,
|
|
397
|
-
}
|
|
398
|
-
onFilterFieldUpdate(updated, groupIdx, filterIdx)
|
|
399
|
-
}}
|
|
400
|
-
/>
|
|
401
380
|
{/if}
|
|
402
381
|
<Select
|
|
403
|
-
value={filter.operator}
|
|
404
|
-
disabled={!filter.field &&
|
|
382
|
+
value={filter.operator || OperatorOptions.Equals.value}
|
|
383
|
+
disabled={!filter.field && !$$slots["field-column"]}
|
|
405
384
|
options={getValidOperatorsForType(filter)}
|
|
406
385
|
on:change={e => {
|
|
407
386
|
const updated = { ...filter, operator: e.detail }
|
|
@@ -411,33 +390,55 @@
|
|
|
411
390
|
placeholder={false}
|
|
412
391
|
popoverAutoWidth
|
|
413
392
|
/>
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
393
|
+
{#if $$slots["extra-column"]}
|
|
394
|
+
<slot
|
|
395
|
+
name="extra-column"
|
|
396
|
+
{filter}
|
|
397
|
+
{groupIdx}
|
|
398
|
+
{filterIdx}
|
|
399
|
+
onUpdate={f =>
|
|
400
|
+
onFilterFieldUpdate(f, groupIdx, filterIdx)}
|
|
401
|
+
{sanitizeOperator}
|
|
402
|
+
{sanitizeValue}
|
|
403
|
+
/>
|
|
404
|
+
{/if}
|
|
405
|
+
{#if $$slots["value-column"]}
|
|
406
|
+
<slot
|
|
407
|
+
name="value-column"
|
|
408
|
+
{filter}
|
|
409
|
+
{groupIdx}
|
|
410
|
+
{filterIdx}
|
|
411
|
+
onUpdate={f =>
|
|
412
|
+
onFilterFieldUpdate(f, groupIdx, filterIdx)}
|
|
413
|
+
{sanitizeOperator}
|
|
414
|
+
{sanitizeValue}
|
|
415
|
+
/>
|
|
416
|
+
{:else}
|
|
417
|
+
<FilterField
|
|
418
|
+
placeholder="Value"
|
|
419
|
+
disabled={!filter.field && !$$slots["field-column"]}
|
|
420
|
+
{drawerTitle}
|
|
421
|
+
{allowBindings}
|
|
422
|
+
filter={{
|
|
423
|
+
...filter,
|
|
424
|
+
}}
|
|
425
|
+
{schemaFields}
|
|
426
|
+
{bindings}
|
|
427
|
+
{panel}
|
|
428
|
+
{toReadable}
|
|
429
|
+
{toRuntime}
|
|
430
|
+
{evaluationContext}
|
|
431
|
+
{bindingValueType}
|
|
432
|
+
{useConditionValueControls}
|
|
433
|
+
on:change={e => {
|
|
434
|
+
onFilterFieldUpdate(
|
|
435
|
+
{ ...filter, ...e.detail },
|
|
436
|
+
groupIdx,
|
|
437
|
+
filterIdx
|
|
438
|
+
)
|
|
439
|
+
}}
|
|
440
|
+
/>
|
|
441
|
+
{/if}
|
|
441
442
|
|
|
442
443
|
<ActionButton
|
|
443
444
|
size="M"
|
|
@@ -478,7 +479,7 @@
|
|
|
478
479
|
popoverAutoWidth
|
|
479
480
|
/>
|
|
480
481
|
</span>
|
|
481
|
-
<span>when all {
|
|
482
|
+
<span>when all {filterTypeLabel}s are empty</span>
|
|
482
483
|
</div>
|
|
483
484
|
{/if}
|
|
484
485
|
<div class="add-group">
|
|
@@ -492,7 +493,7 @@
|
|
|
492
493
|
})
|
|
493
494
|
}}
|
|
494
495
|
>
|
|
495
|
-
Add {
|
|
496
|
+
Add {filterTypeLabel} group
|
|
496
497
|
</Button>
|
|
497
498
|
{#if docsURL}
|
|
498
499
|
<a href={docsURL} target="_blank">
|
|
@@ -563,7 +564,13 @@
|
|
|
563
564
|
.filter {
|
|
564
565
|
display: grid;
|
|
565
566
|
gap: var(--spacing-l);
|
|
566
|
-
grid-template-columns: minmax(150px, 1fr) 170px minmax(200px, 1fr) 40px
|
|
567
|
+
grid-template-columns: minmax(150px, 1fr) 170px minmax(200px, 1fr) 40px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.filter.has-extra-column {
|
|
571
|
+
grid-template-columns:
|
|
572
|
+
minmax(150px, 1fr) 170px 120px minmax(200px, 1fr)
|
|
573
|
+
40px;
|
|
567
574
|
}
|
|
568
575
|
|
|
569
576
|
.filters-footer {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
Icon,
|
|
8
8
|
Drawer,
|
|
9
9
|
Button,
|
|
10
|
+
Select,
|
|
10
11
|
} from "@budibase/bbui"
|
|
11
12
|
|
|
12
13
|
import FilterUsers from "./FilterUsers.svelte"
|
|
@@ -25,6 +26,8 @@
|
|
|
25
26
|
export let toReadable
|
|
26
27
|
export let toRuntime
|
|
27
28
|
export let evaluationContext = {}
|
|
29
|
+
export let bindingValueType = Constants.FilterValueType.BINDING
|
|
30
|
+
export let useConditionValueControls = false
|
|
28
31
|
|
|
29
32
|
const dispatch = createEventDispatcher()
|
|
30
33
|
const { OperatorOptions, FilterValueType } = Constants
|
|
@@ -60,7 +63,7 @@
|
|
|
60
63
|
const onConfirmBinding = () => {
|
|
61
64
|
dispatch("change", {
|
|
62
65
|
value: toRuntime ? toRuntime(bindings, drawerValue) : drawerValue,
|
|
63
|
-
valueType: drawerValue ?
|
|
66
|
+
valueType: drawerValue ? bindingValueType : FilterValueType.VALUE,
|
|
64
67
|
})
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -177,7 +180,14 @@
|
|
|
177
180
|
/>
|
|
178
181
|
{:else}
|
|
179
182
|
<div>
|
|
180
|
-
{#if
|
|
183
|
+
{#if filter.type === FieldType.NUMBER && useConditionValueControls}
|
|
184
|
+
<Input
|
|
185
|
+
type="number"
|
|
186
|
+
disabled={filter.noValue}
|
|
187
|
+
value={readableValue}
|
|
188
|
+
on:change={onChange}
|
|
189
|
+
/>
|
|
190
|
+
{:else if [FieldType.STRING, FieldType.LONGFORM, FieldType.NUMBER, FieldType.BIGINT, FieldType.FORMULA, FieldType.AI, FieldType.BARCODEQR].includes(filter.type)}
|
|
181
191
|
<Input
|
|
182
192
|
disabled={filter.noValue}
|
|
183
193
|
value={readableValue}
|
|
@@ -200,6 +210,18 @@
|
|
|
200
210
|
wrapText
|
|
201
211
|
on:change={onChange}
|
|
202
212
|
/>
|
|
213
|
+
{:else if filter.type === FieldType.BOOLEAN && useConditionValueControls}
|
|
214
|
+
<Select
|
|
215
|
+
placeholder={false}
|
|
216
|
+
disabled={filter.noValue}
|
|
217
|
+
options={[
|
|
218
|
+
{ label: "True", value: "true" },
|
|
219
|
+
{ label: "False", value: "false" },
|
|
220
|
+
]}
|
|
221
|
+
value={readableValue}
|
|
222
|
+
popoverAutoWidth
|
|
223
|
+
on:change={onChange}
|
|
224
|
+
/>
|
|
203
225
|
{:else if filter.type === FieldType.BOOLEAN}
|
|
204
226
|
<Combobox
|
|
205
227
|
disabled={filter.noValue}
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
export let canExpandRows = true
|
|
43
43
|
export let canEditRows = true
|
|
44
44
|
export let canDeleteRows = true
|
|
45
|
+
export let canSelectRows = true
|
|
45
46
|
export let canEditColumns = true
|
|
46
47
|
export let canSaveSchema = true
|
|
47
48
|
export let stripeRows = false
|
|
@@ -102,6 +103,7 @@
|
|
|
102
103
|
canExpandRows,
|
|
103
104
|
canEditRows,
|
|
104
105
|
canDeleteRows,
|
|
106
|
+
canSelectRows,
|
|
105
107
|
canEditColumns,
|
|
106
108
|
canSaveSchema,
|
|
107
109
|
stripeRows,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest"
|
|
2
|
+
import type { UICondition } from "@budibase/types"
|
|
3
|
+
|
|
4
|
+
vi.mock("../../../utils", () => ({
|
|
5
|
+
derivedMemo: (value: unknown) => value,
|
|
6
|
+
QueryUtils: {},
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
import { getEnabledConditions } from "./conditions"
|
|
10
|
+
|
|
11
|
+
describe("grid condition evaluation helpers", () => {
|
|
12
|
+
it("filters disabled conditions", () => {
|
|
13
|
+
const conditions = [
|
|
14
|
+
{ disabled: true },
|
|
15
|
+
{ disabled: false },
|
|
16
|
+
{ disabled: true },
|
|
17
|
+
] as UICondition[]
|
|
18
|
+
|
|
19
|
+
expect(getEnabledConditions(conditions)).toEqual([{ disabled: false }])
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("returns an empty array when no conditions are supplied", () => {
|
|
23
|
+
expect(getEnabledConditions(undefined)).toEqual([])
|
|
24
|
+
expect(getEnabledConditions([])).toEqual([])
|
|
25
|
+
})
|
|
26
|
+
})
|
|
@@ -26,6 +26,13 @@ export const createStores = (): ConditionStore => {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export const getEnabledConditions = (conditions: UICondition[] | undefined) => {
|
|
30
|
+
if (!conditions?.length) {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
return conditions.filter(condition => !condition.disabled)
|
|
34
|
+
}
|
|
35
|
+
|
|
29
36
|
export const deriveStores = (context: StoreContext): ConditionDerivedStore => {
|
|
30
37
|
const { columns, props } = context
|
|
31
38
|
|
|
@@ -77,15 +84,20 @@ export const deriveStores = (context: StoreContext): ConditionDerivedStore => {
|
|
|
77
84
|
export const initialise = (context: StoreContext) => {
|
|
78
85
|
const { metadata, conditions, rows } = context
|
|
79
86
|
|
|
80
|
-
|
|
81
|
-
conditions.subscribe($conditions => {
|
|
87
|
+
const recomputeAllMetadata = () => {
|
|
82
88
|
let newMetadata: Record<string, any> = {}
|
|
89
|
+
const $conditions = get(conditions)
|
|
83
90
|
if ($conditions?.length) {
|
|
84
91
|
for (let row of get(rows)) {
|
|
85
92
|
newMetadata[row._id] = evaluateConditions(row, $conditions, context)
|
|
86
93
|
}
|
|
87
94
|
}
|
|
88
95
|
metadata.set(newMetadata)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Recompute all metadata if conditions change
|
|
99
|
+
conditions.subscribe(() => {
|
|
100
|
+
recomputeAllMetadata()
|
|
89
101
|
})
|
|
90
102
|
|
|
91
103
|
// Recompute metadata for specific rows when they change
|
|
@@ -196,6 +208,8 @@ const evaluateConditions = (
|
|
|
196
208
|
}
|
|
197
209
|
}
|
|
198
210
|
|
|
211
|
+
allConditions = getEnabledConditions(allConditions)
|
|
212
|
+
|
|
199
213
|
// Pre-process button conditions to set default visibility for show conditions
|
|
200
214
|
const buttonShowConditions = new Set()
|
|
201
215
|
for (let condition of allConditions) {
|
|
@@ -82,9 +82,8 @@ export const deriveStores = (context: StoreContext): ConfigDerivedStore => {
|
|
|
82
82
|
config.canEditColumns = false
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// Determine if we can select rows.
|
|
86
|
-
|
|
87
|
-
config.canSelectRows = true
|
|
85
|
+
// Determine if we can select rows.
|
|
86
|
+
config.canSelectRows = $props.canSelectRows !== false
|
|
88
87
|
|
|
89
88
|
return config
|
|
90
89
|
}
|
package/src/fetch/QueryFetch.ts
CHANGED
|
@@ -7,9 +7,10 @@ export default class QueryFetch extends BaseDataFetch<QueryDatasource, Query> {
|
|
|
7
7
|
async determineFeatureFlags() {
|
|
8
8
|
const definition = await this.getDefinition()
|
|
9
9
|
const supportsPagination =
|
|
10
|
-
!!definition?.fields?.pagination?.type &&
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
(!!definition?.fields?.pagination?.type &&
|
|
11
|
+
!!definition?.fields?.pagination?.location &&
|
|
12
|
+
!!definition?.fields?.pagination?.pageParam) ||
|
|
13
|
+
!!definition?.fields?.pagination?.enabled
|
|
13
14
|
return { supportsPagination }
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -41,7 +42,7 @@ export default class QueryFetch extends BaseDataFetch<QueryDatasource, Query> {
|
|
|
41
42
|
const { datasource, limit, paginate } = this.options
|
|
42
43
|
const { supportsPagination } = this.features
|
|
43
44
|
const { cursor, definition } = get(this.store)
|
|
44
|
-
const
|
|
45
|
+
const paginationType = definition?.fields?.pagination?.type
|
|
45
46
|
|
|
46
47
|
// Set the default query params
|
|
47
48
|
const parameters = Helpers.cloneDeep(datasource.queryParams || {})
|
|
@@ -54,7 +55,11 @@ export default class QueryFetch extends BaseDataFetch<QueryDatasource, Query> {
|
|
|
54
55
|
// Add pagination to query if supported
|
|
55
56
|
const queryPayload: ExecuteQueryRequest = { parameters }
|
|
56
57
|
if (paginate && supportsPagination) {
|
|
57
|
-
|
|
58
|
+
// For SQL queries (pagination.enabled), use page-based pagination
|
|
59
|
+
// For REST queries, use the configured type (page or cursor)
|
|
60
|
+
const isPageBased =
|
|
61
|
+
paginationType === "page" || definition?.fields?.pagination?.enabled
|
|
62
|
+
const requestCursor = isPageBased ? parseInt(cursor || "1") : cursor
|
|
58
63
|
queryPayload.pagination = { page: requestCursor, limit }
|
|
59
64
|
}
|
|
60
65
|
|
|
@@ -67,7 +72,10 @@ export default class QueryFetch extends BaseDataFetch<QueryDatasource, Query> {
|
|
|
67
72
|
let nextCursor = null
|
|
68
73
|
let hasNextPage = false
|
|
69
74
|
if (paginate && supportsPagination) {
|
|
70
|
-
|
|
75
|
+
const isPageBased =
|
|
76
|
+
paginationType === "page" || definition?.fields?.pagination?.enabled
|
|
77
|
+
|
|
78
|
+
if (isPageBased) {
|
|
71
79
|
// For "page number" pagination, increment the existing page number
|
|
72
80
|
nextCursor = queryPayload.pagination!.page! + 1
|
|
73
81
|
hasNextPage = data?.length === limit && limit > 0
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
import { Input, Icon, Drawer, Button } from "@budibase/bbui"
|
|
3
|
-
import { isJSBinding } from "@budibase/string-templates"
|
|
4
|
-
import { createEventDispatcher } from "svelte"
|
|
5
|
-
|
|
6
|
-
export let filter
|
|
7
|
-
export let disabled = false
|
|
8
|
-
export let bindings = []
|
|
9
|
-
export let panel
|
|
10
|
-
export let drawerTitle
|
|
11
|
-
export let toReadable
|
|
12
|
-
export let toRuntime
|
|
13
|
-
export let evaluationContext = {}
|
|
14
|
-
|
|
15
|
-
const dispatch = createEventDispatcher()
|
|
16
|
-
|
|
17
|
-
let bindingDrawer
|
|
18
|
-
let fieldValue
|
|
19
|
-
|
|
20
|
-
$: fieldValue = filter?.field
|
|
21
|
-
$: readableValue = toReadable ? toReadable(bindings, fieldValue) : fieldValue
|
|
22
|
-
$: drawerValue = toDrawerValue(fieldValue)
|
|
23
|
-
$: isJS = isJSBinding(fieldValue)
|
|
24
|
-
|
|
25
|
-
const drawerOnChange = e => {
|
|
26
|
-
drawerValue = e.detail
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const onChange = e => {
|
|
30
|
-
fieldValue = e.detail
|
|
31
|
-
dispatch("change", {
|
|
32
|
-
field: toRuntime ? toRuntime(bindings, fieldValue) : fieldValue,
|
|
33
|
-
})
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const onConfirmBinding = () => {
|
|
37
|
-
dispatch("change", {
|
|
38
|
-
field: toRuntime ? toRuntime(bindings, drawerValue) : drawerValue,
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Converts arrays into strings. The CodeEditor expects a string or encoded JS
|
|
44
|
-
*
|
|
45
|
-
* @param{string} fieldValue
|
|
46
|
-
*/
|
|
47
|
-
const toDrawerValue = fieldValue => {
|
|
48
|
-
return Array.isArray(fieldValue) ? fieldValue.join(",") : readableValue
|
|
49
|
-
}
|
|
50
|
-
</script>
|
|
51
|
-
|
|
52
|
-
<div>
|
|
53
|
-
<Drawer
|
|
54
|
-
on:drawerHide
|
|
55
|
-
on:drawerShow
|
|
56
|
-
bind:this={bindingDrawer}
|
|
57
|
-
title={drawerTitle || ""}
|
|
58
|
-
forceModal
|
|
59
|
-
>
|
|
60
|
-
<Button
|
|
61
|
-
cta
|
|
62
|
-
slot="buttons"
|
|
63
|
-
on:click={() => {
|
|
64
|
-
onConfirmBinding()
|
|
65
|
-
bindingDrawer.hide()
|
|
66
|
-
}}
|
|
67
|
-
>
|
|
68
|
-
Confirm
|
|
69
|
-
</Button>
|
|
70
|
-
<svelte:component
|
|
71
|
-
this={panel}
|
|
72
|
-
slot="body"
|
|
73
|
-
value={drawerValue}
|
|
74
|
-
allowJS
|
|
75
|
-
allowHelpers
|
|
76
|
-
allowHBS
|
|
77
|
-
on:change={drawerOnChange}
|
|
78
|
-
{bindings}
|
|
79
|
-
context={evaluationContext}
|
|
80
|
-
/>
|
|
81
|
-
</Drawer>
|
|
82
|
-
|
|
83
|
-
<div class="field-wrap" class:bindings={true}>
|
|
84
|
-
<div class="field">
|
|
85
|
-
<Input
|
|
86
|
-
disabled={filter.noValue}
|
|
87
|
-
readonly={isJS}
|
|
88
|
-
value={isJS ? "(JavaScript function)" : readableValue}
|
|
89
|
-
on:change={onChange}
|
|
90
|
-
/>
|
|
91
|
-
</div>
|
|
92
|
-
|
|
93
|
-
<div class="binding-control">
|
|
94
|
-
{#if !disabled}
|
|
95
|
-
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
96
|
-
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
97
|
-
<div
|
|
98
|
-
class="icon binding"
|
|
99
|
-
on:click={() => {
|
|
100
|
-
bindingDrawer.show()
|
|
101
|
-
}}
|
|
102
|
-
>
|
|
103
|
-
<Icon size="S" weight="fill" name="lightning" />
|
|
104
|
-
</div>
|
|
105
|
-
{/if}
|
|
106
|
-
</div>
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
109
|
-
|
|
110
|
-
<style>
|
|
111
|
-
.field-wrap {
|
|
112
|
-
display: flex;
|
|
113
|
-
}
|
|
114
|
-
.field {
|
|
115
|
-
flex: 1;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.field-wrap.bindings .field :global(.spectrum-Form-itemField),
|
|
119
|
-
.field-wrap.bindings .field :global(input),
|
|
120
|
-
.field-wrap.bindings .field :global(.spectrum-Picker) {
|
|
121
|
-
border-top-right-radius: 0px;
|
|
122
|
-
border-bottom-right-radius: 0px;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.field-wrap.bindings
|
|
126
|
-
.field
|
|
127
|
-
:global(.spectrum-InputGroup.spectrum-Datepicker) {
|
|
128
|
-
min-width: unset;
|
|
129
|
-
border-radius: 0px;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.field-wrap.bindings
|
|
133
|
-
.field
|
|
134
|
-
:global(
|
|
135
|
-
.spectrum-InputGroup.spectrum-Datepicker
|
|
136
|
-
.spectrum-Textfield-input.spectrum-InputGroup-input
|
|
137
|
-
) {
|
|
138
|
-
width: 100%;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.binding-control .icon {
|
|
142
|
-
border: 1px solid
|
|
143
|
-
var(
|
|
144
|
-
--spectrum-textfield-m-border-color,
|
|
145
|
-
var(--spectrum-alias-border-color)
|
|
146
|
-
);
|
|
147
|
-
border-left: 0px;
|
|
148
|
-
border-top-right-radius: 4px;
|
|
149
|
-
border-bottom-right-radius: 4px;
|
|
150
|
-
justify-content: center;
|
|
151
|
-
align-items: center;
|
|
152
|
-
display: flex;
|
|
153
|
-
flex-direction: row;
|
|
154
|
-
box-sizing: border-box;
|
|
155
|
-
width: 31px;
|
|
156
|
-
color: var(--spectrum-alias-text-color);
|
|
157
|
-
background-color: var(--spectrum-global-color-gray-75);
|
|
158
|
-
transition:
|
|
159
|
-
background-color var(--spectrum-global-animation-duration-100, 130ms),
|
|
160
|
-
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
|
|
161
|
-
border-color var(--spectrum-global-animation-duration-100, 130ms);
|
|
162
|
-
height: calc(var(--spectrum-alias-item-height-m));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.binding-control .icon:hover {
|
|
166
|
-
cursor: pointer;
|
|
167
|
-
background-color: var(--spectrum-global-color-gray-50);
|
|
168
|
-
border-color: var(--spectrum-alias-border-color-hover);
|
|
169
|
-
color: var(--spectrum-alias-text-color-hover);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.binding-control .icon.binding:hover {
|
|
173
|
-
color: var(--yellow);
|
|
174
|
-
}
|
|
175
|
-
</style>
|