-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
QueryLibrary model and reducer (#1239)
* add QueryLibrary reducer Signed-off-by: Mason Fish <mason@looky.cloud> * add uncle test Signed-off-by: Mason Fish <mason@looky.cloud> * adjust pr comments Signed-off-by: Mason Fish <mason@looky.cloud> * change queryLibrary to queries Signed-off-by: Mason Fish <mason@looky.cloud> Co-authored-by: Mason Fish <mason@looky.cloud>
- Loading branch information
1 parent
706d5dc
commit 7dc9730
Showing
9 changed files
with
417 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { | ||
Group, | ||
QUERIES_ADD_ITEM, | ||
QUERIES_EDIT_ITEM, | ||
QUERIES_MOVE_ITEM, | ||
QUERIES_REMOVE_ITEM, | ||
QUERIES_SET_ALL, | ||
Query | ||
} from "./types" | ||
|
||
export default { | ||
setAll: (rootGroup: Group): QUERIES_SET_ALL => ({ | ||
type: "QUERIES_SET_ALL", | ||
rootGroup | ||
}), | ||
addItem: (item: Query | Group, groupPath: number[]): QUERIES_ADD_ITEM => ({ | ||
type: "QUERIES_ADD_ITEM", | ||
item, | ||
groupPath | ||
}), | ||
removeItem: (itemPath: number[]): QUERIES_REMOVE_ITEM => ({ | ||
type: "QUERIES_REMOVE_ITEM", | ||
itemPath | ||
}), | ||
editItem: (item: Query | Group, itemPath: number[]): QUERIES_EDIT_ITEM => ({ | ||
type: "QUERIES_EDIT_ITEM", | ||
item, | ||
itemPath | ||
}), | ||
moveItem: ( | ||
srcItemPath: number[], | ||
destItemPath: number[] | ||
): QUERIES_MOVE_ITEM => ({ | ||
type: "QUERIES_MOVE_ITEM", | ||
srcItemPath, | ||
destItemPath | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import actions from "./actions" | ||
import reducer from "./reducer" | ||
import selectors from "./selectors" | ||
|
||
export default { | ||
...actions, | ||
...selectors, | ||
reducer | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import {QueriesState} from "./types" | ||
|
||
const init = (): QueriesState => ({ | ||
id: "root", | ||
name: "root", | ||
items: [] | ||
}) | ||
|
||
export default init |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import {Group, Query, QueriesAction, QueriesState} from "./types" | ||
import produce from "immer" | ||
import {get, set, initial, last, isEqual} from "lodash" | ||
import init from "./initial" | ||
|
||
export default produce((draft: QueriesState, action: QueriesAction) => { | ||
switch (action.type) { | ||
case "QUERIES_SET_ALL": | ||
return action.rootGroup | ||
case "QUERIES_ADD_ITEM": | ||
addItemToGroup(draft, action.groupPath, action.item) | ||
return | ||
case "QUERIES_REMOVE_ITEM": | ||
removeItemFromGroup(draft, action.itemPath) | ||
return | ||
case "QUERIES_EDIT_ITEM": | ||
if (!get(draft, toItemPath(action.itemPath), null)) return | ||
|
||
set(draft, toItemPath(action.itemPath), action.item) | ||
return | ||
case "QUERIES_MOVE_ITEM": | ||
moveItem(draft, action.srcItemPath, action.destItemPath) | ||
return | ||
} | ||
}, init()) | ||
|
||
const toItemPath = (path: number[]): string => | ||
path.map((pathNdx) => `items[${pathNdx}]`).join(".") | ||
|
||
const addItemToGroup = ( | ||
draft: QueriesState, | ||
groupPath: number[], | ||
item: Query | Group, | ||
index?: number | ||
): void => { | ||
const parentGroup = get(draft, toItemPath(groupPath), null) | ||
if (!parentGroup) return | ||
|
||
if (typeof index === "undefined") { | ||
parentGroup.items.push(item) | ||
return | ||
} | ||
|
||
parentGroup.items.splice(index, 0, item) | ||
} | ||
|
||
const removeItemFromGroup = (draft: QueriesState, itemPath: number[]): void => { | ||
const parentGroup = get(draft, toItemPath(initial(itemPath)), null) | ||
if (!parentGroup) return | ||
|
||
parentGroup.items.splice(last(itemPath), 1) | ||
} | ||
|
||
const moveItem = ( | ||
draft: QueriesState, | ||
srcItemPath: number[], | ||
destItemPath: number[] | ||
): void => { | ||
const srcItem = get(draft, toItemPath(srcItemPath), null) | ||
|
||
if (!srcItem) return | ||
if (!get(draft, toItemPath(initial(destItemPath)), null)) return | ||
|
||
// If the move is all in the same directory then the adjusting indices can | ||
// cause an off by one issue since the destination index will be affected after | ||
// removal (e.g. an item cannot be moved to the end of its current group because of this). | ||
// For this situation we instead remove the item first, and then insert its copy | ||
if (isEqual(initial(srcItemPath), initial(destItemPath))) { | ||
removeItemFromGroup(draft, srcItemPath) | ||
addItemToGroup( | ||
draft, | ||
initial(destItemPath), | ||
{...srcItem}, | ||
last(destItemPath) | ||
) | ||
} else { | ||
addItemToGroup( | ||
draft, | ||
initial(destItemPath), | ||
{...srcItem}, | ||
last(destItemPath) | ||
) | ||
removeItemFromGroup(draft, srcItemPath) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import {QueriesState} from "./types" | ||
import {State} from "../types" | ||
|
||
export default { | ||
getRaw: (state: State): QueriesState => state.queries | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
import initTestStore from "../../test/initTestStore" | ||
import Queries from "./" | ||
import {Group} from "./types" | ||
import get from "lodash/get" | ||
import {State} from "../types" | ||
|
||
let store | ||
beforeEach(() => { | ||
store = initTestStore() | ||
}) | ||
|
||
const testLib = { | ||
id: "root", | ||
name: "root", | ||
items: [ | ||
{ | ||
// .items[0] | ||
id: "testId1", | ||
name: "testName1", | ||
items: [ | ||
{ | ||
// .items[0].items[0] | ||
id: "testId2", | ||
name: "testName2", | ||
description: "testDescription2", | ||
zql: "testValue2", | ||
tags: ["testTag1", "testTag2"] | ||
}, | ||
{ | ||
// .items[0].items[1] | ||
id: "testId3", | ||
name: "testName3", | ||
items: [ | ||
{ | ||
// .items[0].items[1].items[0] | ||
id: "testId4", | ||
name: "testName4", | ||
description: "testDescription4", | ||
zql: "testValue4", | ||
tags: ["testTag2"] | ||
} | ||
] | ||
}, | ||
{ | ||
// .items[0].items[2] | ||
id: "testId5", | ||
name: "testName5", | ||
description: "testDescription5", | ||
zql: "testValue5", | ||
tags: ["testTag1"] | ||
} | ||
] | ||
} | ||
] | ||
} | ||
|
||
const newQuery = { | ||
id: "newQueryId", | ||
name: "newQueryName", | ||
description: "newQueryDescription", | ||
zql: "newQueryValue", | ||
tags: [] | ||
} | ||
|
||
const newGroup = { | ||
id: "newGroupId", | ||
name: "newGroupName", | ||
items: [] | ||
} | ||
|
||
const getGroup = (state: State, path: number[]): Group => { | ||
return get( | ||
Queries.getRaw(state), | ||
path.map((pathNdx) => `items[${pathNdx}]`).join(".") | ||
) | ||
} | ||
|
||
test("set all", () => { | ||
store.dispatch(Queries.setAll(testLib)) | ||
|
||
const state = store.getState() | ||
|
||
expect(Queries.getRaw(state)).toEqual(testLib) | ||
}) | ||
|
||
test("add query", () => { | ||
store.dispatch(Queries.setAll(testLib)) | ||
|
||
expect(getGroup(store.getState(), [0]).items).toHaveLength(3) | ||
|
||
store.dispatch(Queries.addItem(newQuery, [0])) | ||
|
||
expect(getGroup(store.getState(), [0]).items).toHaveLength(4) | ||
expect(getGroup(store.getState(), [0]).items[3]).toEqual(newQuery) | ||
}) | ||
|
||
test("add query, nested", () => { | ||
store.dispatch(Queries.setAll(testLib)) | ||
|
||
expect(getGroup(store.getState(), [0, 1]).items).toHaveLength(1) | ||
|
||
store.dispatch(Queries.addItem(newQuery, [0, 1])) | ||
|
||
expect(getGroup(store.getState(), [0, 1]).items).toHaveLength(2) | ||
expect(getGroup(store.getState(), [0, 1]).items[1]).toEqual(newQuery) | ||
}) | ||
|
||
test("add group, add query to new group", () => { | ||
store.dispatch(Queries.setAll(testLib)) | ||
|
||
expect(getGroup(store.getState(), [0]).items).toHaveLength(3) | ||
|
||
store.dispatch(Queries.addItem(newGroup, [0])) | ||
|
||
expect(getGroup(store.getState(), [0]).items).toHaveLength(4) | ||
expect(getGroup(store.getState(), [0]).items[3]).toEqual(newGroup) | ||
expect(getGroup(store.getState(), [0, 3]).items).toHaveLength(0) | ||
|
||
store.dispatch(Queries.addItem(newQuery, [0, 3])) | ||
|
||
expect(getGroup(store.getState(), [0, 3]).items).toHaveLength(1) | ||
expect(getGroup(store.getState(), [0, 3]).items[0]).toEqual(newQuery) | ||
}) | ||
|
||
test("remove query, group", () => { | ||
store.dispatch(Queries.setAll(testLib)) | ||
|
||
const testName1Group = getGroup(store.getState(), [0]).items | ||
expect(testName1Group).toHaveLength(3) | ||
|
||
store.dispatch(Queries.removeItem([0, 0])) | ||
|
||
expect(getGroup(store.getState(), [0]).items).toHaveLength(2) | ||
expect(getGroup(store.getState(), [0]).items).toEqual(testName1Group.slice(1)) | ||
|
||
store.dispatch(Queries.removeItem([0, 0])) | ||
expect(getGroup(store.getState(), [0]).items).toHaveLength(1) | ||
expect(getGroup(store.getState(), [0]).items).toEqual([testName1Group[2]]) | ||
}) | ||
|
||
test("move query, same group, different group same depth", () => { | ||
store.dispatch(Queries.setAll(testLib)) | ||
|
||
const testName1Group = getGroup(store.getState(), [0]).items | ||
expect(testName1Group).toHaveLength(3) | ||
|
||
const testName2Query = testName1Group[0] | ||
|
||
// move to end | ||
store.dispatch(Queries.moveItem([0, 0], [0, 2])) | ||
|
||
expect(getGroup(store.getState(), [0]).items).toHaveLength(3) | ||
|
||
expect(getGroup(store.getState(), [0]).items).toEqual([ | ||
...testName1Group.slice(1), | ||
testName2Query | ||
]) | ||
|
||
// move back to beginning | ||
store.dispatch(Queries.moveItem([0, 2], [0, 0])) | ||
|
||
expect(getGroup(store.getState(), [0]).items).toHaveLength(3) | ||
expect(getGroup(store.getState(), [0]).items).toEqual(testName1Group) | ||
|
||
// move to "uncle's" group | ||
store.dispatch(Queries.addItem(newGroup, [0])) | ||
|
||
expect(getGroup(store.getState(), [0, 1]).items).toHaveLength(1) | ||
expect(getGroup(store.getState(), [0, 3]).items).toHaveLength(0) | ||
|
||
const testName4Query = getGroup(store.getState(), [0, 1]).items[0] | ||
|
||
store.dispatch(Queries.moveItem([0, 1, 0], [0, 3, 0])) | ||
|
||
expect(getGroup(store.getState(), [0, 1]).items).toHaveLength(0) | ||
expect(getGroup(store.getState(), [0, 3]).items).toHaveLength(1) | ||
expect(getGroup(store.getState(), [0, 3]).items[0]).toEqual(testName4Query) | ||
}) | ||
|
||
test("move query, different group", () => { | ||
store.dispatch(Queries.setAll(testLib)) | ||
|
||
const testName1Group = getGroup(store.getState(), [0]).items | ||
const testName3Group = (testName1Group[1] as Group).items | ||
|
||
expect(testName1Group).toHaveLength(3) | ||
expect(testName3Group).toHaveLength(1) | ||
|
||
const testName2Query = testName1Group[0] | ||
|
||
store.dispatch(Queries.moveItem([0, 0], [0, 1, 0])) | ||
|
||
const newTestName1Group = getGroup(store.getState(), [0]).items | ||
const newTestName3Group = (newTestName1Group[0] as Group).items | ||
|
||
expect(newTestName1Group).toHaveLength(2) | ||
expect(newTestName3Group).toHaveLength(2) | ||
|
||
expect(newTestName1Group[0].id).toEqual(testName1Group[1].id) | ||
expect(newTestName3Group).toEqual([testName2Query, ...testName3Group]) | ||
}) | ||
|
||
test("edit query", () => { | ||
store.dispatch(Queries.setAll(testLib)) | ||
|
||
store.dispatch(Queries.editItem(newQuery, [0, 0])) | ||
expect(getGroup(store.getState(), [0]).items[0]).toEqual(newQuery) | ||
}) | ||
|
||
test("edit group", () => { | ||
store.dispatch(Queries.setAll(testLib)) | ||
|
||
store.dispatch(Queries.editItem(newGroup, [0, 1])) | ||
expect(getGroup(store.getState(), [0, 1])).toEqual(newGroup) | ||
}) |
Oops, something went wrong.