diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte
index 47f50c2739..78e8da400b 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)
+ } 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..d45c9d4c8e 100644
--- a/packages/builder/src/stores/backend/queries.js
+++ b/packages/builder/src/stores/backend/queries.js
@@ -1,14 +1,19 @@
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,
- set,
- update,
+ const actions = {
init: async () => {
const response = await api.get(`/api/queries`)
const json = await response.json()
@@ -17,6 +22,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 +55,7 @@ export function createQueriesStore() {
} else {
queries.push(json)
}
+ sortQueries(queries)
return { list: queries, selected: json._id }
})
return json
@@ -76,6 +83,27 @@ export function createQueriesStore() {
})
return response
},
+ duplicate: async query => {
+ 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)
+ )
+
+ actions.save(datasourceId, newQuery)
+ },
+ }
+
+ return {
+ subscribe,
+ set,
+ update,
+ ...actions,
}
}
diff --git a/packages/builder/src/stores/backend/tests/queries.spec.js b/packages/builder/src/stores/backend/tests/queries.spec.js
index 1d1a1d0154..b4c1805c66 100644
--- a/packages/builder/src/stores/backend/tests/queries.spec.js
+++ b/packages/builder/src/stores/backend/tests/queries.spec.js
@@ -6,7 +6,6 @@ jest.mock('builderStore/api');
import { SOME_QUERY, SAVE_QUERY_RESPONSE } from './fixtures/queries'
import { createQueriesStore } from "../queries"
-import { datasources } from '../datasources'
describe("Queries Store", () => {
let store = createQueriesStore()