diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte
index 47f50c2739..98bc7e6c83 100644
--- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte
+++ b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte
@@ -11,6 +11,14 @@
await queries.delete(query)
notifications.success("Query deleted")
}
+
+ async function duplicateQuery() {
+ try {
+ await queries.duplicate(query, queries.save)
+ } catch (e) {
+ notifications.error(e.message)
+ }
+ }
@@ -18,6 +26,7 @@
+
{
+ const baseName = name.split(" (")[0]
+ const isDuplicate = new RegExp(`${baseName}\\s\\((\\d+)\\)$`)
+
+ // get the sequence from matched names
+ const sequence = []
+ allNames.filter(n => {
+ if (n === baseName) {
+ return true
+ }
+ const match = n.match(isDuplicate)
+ if (match) {
+ sequence.push(parseInt(match[1]))
+ return true
+ }
+ return false
+ })
+ sequence.sort((a, b) => a - b)
+
+ // get the next number in the sequence
+ let number
+ if (sequence.length === 0) {
+ number = 1
+ } else {
+ // get the next number in the sequence
+ for (let i = 0; i < sequence.length; i++) {
+ if (sequence[i] !== i + 1) {
+ number = i + 1
+ break
+ }
+ }
+ if (!number) {
+ number = sequence.length + 1
+ }
+ }
+
+ return `${baseName} (${number})`
+}
diff --git a/packages/builder/src/helpers/tests/duplicate.spec.js b/packages/builder/src/helpers/tests/duplicate.spec.js
new file mode 100644
index 0000000000..cecf35b545
--- /dev/null
+++ b/packages/builder/src/helpers/tests/duplicate.spec.js
@@ -0,0 +1,42 @@
+const { duplicateName } = require("../duplicate")
+
+describe("duplicate", () => {
+
+ describe("duplicates a name ", () => {
+ it("with a single existing", async () => {
+ const names = ["foo"]
+ const name = "foo"
+
+ const duplicate = duplicateName(name, names)
+
+ expect(duplicate).toBe("foo (1)")
+ })
+
+ it("with multiple existing", async () => {
+ const names = ["foo", "foo (1)", "foo (2)"]
+ const name = "foo"
+
+ const duplicate = duplicateName(name, names)
+
+ expect(duplicate).toBe("foo (3)")
+ })
+
+ it("with mixed multiple existing", async () => {
+ const names = ["foo", "foo (1)", "foo (2)", "bar", "bar (1)", "bar (2)"]
+ const name = "foo"
+
+ const duplicate = duplicateName(name, names)
+
+ expect(duplicate).toBe("foo (3)")
+ })
+
+ it("with incomplete sequence", async () => {
+ const names = ["foo", "foo (2)", "foo (3)"]
+ const name = "foo"
+
+ const duplicate = duplicateName(name, names)
+
+ expect(duplicate).toBe("foo (1)")
+ })
+ })
+})
diff --git a/packages/builder/src/stores/backend/queries.js b/packages/builder/src/stores/backend/queries.js
index 020a0c9420..d07f6d2f6c 100644
--- a/packages/builder/src/stores/backend/queries.js
+++ b/packages/builder/src/stores/backend/queries.js
@@ -1,9 +1,17 @@
import { writable, get } from "svelte/store"
import { datasources, integrations, tables, views } from "./"
import api from "builderStore/api"
+import { duplicateName } from "../../helpers/duplicate"
+
+const sortQueries = queryList => {
+ queryList.sort((q1, q2) => {
+ return q1.name.localeCompare(q2.name)
+ })
+}
export function createQueriesStore() {
- const { subscribe, set, update } = writable({ list: [], selected: null })
+ const store = writable({ list: [], selected: null })
+ const { subscribe, set, update } = store
return {
subscribe,
@@ -17,6 +25,7 @@ export function createQueriesStore() {
fetch: async () => {
const response = await api.get(`/api/queries`)
const json = await response.json()
+ sortQueries(json)
update(state => ({ ...state, list: json }))
return json
},
@@ -49,6 +58,7 @@ export function createQueriesStore() {
} else {
queries.push(json)
}
+ sortQueries(queries)
return { list: queries, selected: json._id }
})
return json
@@ -76,6 +86,20 @@ export function createQueriesStore() {
})
return response
},
+ duplicate: async (query, saveFn) => {
+ let list = get(store).list
+ const newQuery = { ...query }
+ const datasourceId = query.datasourceId
+
+ delete newQuery._id
+ delete newQuery._rev
+ newQuery.name = duplicateName(
+ query.name,
+ list.map(q => q.name)
+ )
+
+ saveFn(datasourceId, newQuery)
+ },
}
}