feat: Update order of action file operations (#22754)
## Description In order to improve new user experience, we want to update the order of items listed in the new action list. This will show app datasources higher in the order and generic creation action lower in the order. Create JS objects is still listed on the top. This also will update the list sorting on the omni bar. <img width="524" alt="Screenshot 2023-04-26 at 3 49 31 PM" src="https://user-images.githubusercontent.com/12022471/234547215-c15c8f12-7be1-4462-8b78-190e7dc75dea.png"> <img width="513" alt="Screenshot 2023-04-26 at 3 53 39 PM" src="https://user-images.githubusercontent.com/12022471/234547574-46308912-28de-49f7-a3bf-f872def42adb.png"> > Improve the order of action create list Fixes #22618 ## Type of change - New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Extracted the function and adding some jest cases for the functionality - Manual - Jest - Cypress ### Test Plan TBA ### Issues raised during DP testing https://github.com/appsmithorg/appsmith/pull/22754#issuecomment-1527503666 ## Checklist: ### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag ### QA activity: - [ ] Test plan has been approved by relevant developers - [x] Test plan has been peer reviewed by QA - [ ] Cypress test cases have been added and approved by either SDET or manual QA - [ ] Organized project review call with relevant stakeholders after Round 1/2 of QA - [x] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
parent
1c56186b53
commit
a4dec4bb6e
|
|
@ -3,7 +3,7 @@ const dsl = require("../../../../fixtures/omnibarDsl.json");
|
|||
const commonlocators = require("../../../../locators/commonlocators.json");
|
||||
import { ObjectsRegistry } from "../../../../support/Objects/Registry";
|
||||
|
||||
const locators = ObjectsRegistry.CommonLocators;
|
||||
const ee = ObjectsRegistry.EntityExplorer;
|
||||
|
||||
describe("Omnibar functionality test cases", () => {
|
||||
const apiName = "Omnibar1";
|
||||
|
|
@ -81,31 +81,34 @@ describe("Omnibar functionality test cases", () => {
|
|||
"createNewJSCollection",
|
||||
);
|
||||
cy.get(omnibar.categoryTitle).eq(1).click();
|
||||
// create new api, js object and cURL import from omnibar
|
||||
cy.get(omnibar.createNew).eq(0).should("have.text", "New Blank API");
|
||||
|
||||
// 2 is the index value of the JS Object in omnibar ui
|
||||
cy.get(omnibar.createNew).eq(2).should("have.text", "New JS Object");
|
||||
// create new api, js object and cURL import from omnibar
|
||||
|
||||
// 0 is the index value of the JS Object in omnibar ui
|
||||
cy.get(omnibar.createNew).eq(0).should("have.text", "New JS Object");
|
||||
// 1 is the index value of the JS Object in omnibar ui
|
||||
cy.get(omnibar.createNew).eq(1).should("have.text", "New Blank API");
|
||||
// 3 is the index value of the Curl import in omnibar ui
|
||||
cy.get(omnibar.createNew).eq(3).should("have.text", "New cURL Import");
|
||||
|
||||
cy.get(omnibar.createNew).eq(0).click();
|
||||
cy.wait(1000);
|
||||
cy.wait("@createNewApi");
|
||||
cy.renameWithInPane(apiName);
|
||||
cy.get(omnibar.globalSearch).click({ force: true });
|
||||
cy.get(omnibar.categoryTitle).eq(1).click();
|
||||
// 2 is the index value of the JS Object in omnibar ui
|
||||
cy.get(omnibar.createNew).eq(2).click();
|
||||
cy.wait(1000);
|
||||
cy.wait("@createNewJSCollection");
|
||||
cy.wait(1000);
|
||||
cy.get(".t--js-action-name-edit-field").type(jsObjectName).wait(1000);
|
||||
cy.get(omnibar.globalSearch).click({ force: true });
|
||||
cy.get(omnibar.categoryTitle).eq(1).click();
|
||||
cy.wait(1000);
|
||||
// 3 is the index value of the JS Object in omnibar ui
|
||||
cy.get(omnibar.createNew).eq(3).click();
|
||||
|
||||
cy.get(omnibar.createNew).eq(1).click();
|
||||
cy.wait(1000);
|
||||
cy.wait("@createNewApi");
|
||||
cy.renameWithInPane(apiName);
|
||||
cy.get(omnibar.globalSearch).click({ force: true });
|
||||
cy.get(omnibar.categoryTitle).eq(1).click();
|
||||
|
||||
cy.wait(1000);
|
||||
cy.get(omnibar.createNew).eq(3).click();
|
||||
cy.url().should("include", "curl-import?");
|
||||
cy.get('p:contains("Import from CURL")').should("be.visible");
|
||||
});
|
||||
|
|
@ -133,8 +136,7 @@ describe("Omnibar functionality test cases", () => {
|
|||
});
|
||||
|
||||
it("6. Verify Navigate section shows recently opened widgets and datasources", function () {
|
||||
cy.get(".bp3-icon-chevron-left").click({ force: true });
|
||||
cy.openPropertyPane("buttonwidget");
|
||||
ee.SelectEntityByName("Button1", "Widgets");
|
||||
cy.get(omnibar.globalSearch).click({ force: true });
|
||||
cy.get(omnibar.categoryTitle).eq(0).click();
|
||||
// verify recently opened items with their subtext i.e page name
|
||||
|
|
@ -146,13 +148,13 @@ describe("Omnibar functionality test cases", () => {
|
|||
|
||||
cy.xpath(omnibar.recentlyopenItem)
|
||||
.eq(1)
|
||||
.should("have.text", "Omnibar2")
|
||||
.should("have.text", "Omnibar1")
|
||||
.next()
|
||||
.should("have.text", "Page1");
|
||||
|
||||
cy.xpath(omnibar.recentlyopenItem)
|
||||
.eq(2)
|
||||
.should("have.text", "Omnibar1")
|
||||
.should("have.text", "Omnibar2")
|
||||
.next()
|
||||
.should("have.text", "Page1");
|
||||
|
||||
|
|
|
|||
|
|
@ -234,9 +234,13 @@ export class EntityExplorer {
|
|||
|
||||
public CreateNewDsQuery(dsName: string, isQuery = true) {
|
||||
cy.get(this.locator._createNew).last().click({ force: true });
|
||||
let overlayItem = isQuery
|
||||
? this._visibleTextSpan(dsName + " Query")
|
||||
: this._visibleTextSpan(dsName);
|
||||
const searchText = isQuery ? dsName + " query" : dsName;
|
||||
this.SearchAndClickOmnibar(searchText);
|
||||
}
|
||||
|
||||
public SearchAndClickOmnibar(searchText: string) {
|
||||
cy.get(`[data-testId="t--search-file-operation"]`).type(searchText);
|
||||
let overlayItem = this._visibleTextSpan(searchText);
|
||||
this.agHelper.GetNClick(overlayItem);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -706,8 +706,8 @@ Cypress.Commands.add("NavigateToWidgetsInExplorer", () => {
|
|||
|
||||
Cypress.Commands.add("NavigateToJSEditor", () => {
|
||||
cy.get(explorer.createNew).click({ force: true });
|
||||
// 2 is the index value of the JS Object in omnibar ui
|
||||
cy.get(".t--file-operation").eq(2).click({ force: true });
|
||||
cy.get(`[data-testId="t--search-file-operation"]`).type("New JS Object");
|
||||
cy.get(".t--file-operation").eq(0).click({ force: true });
|
||||
});
|
||||
|
||||
Cypress.Commands.add("importCurl", () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,249 @@
|
|||
import { getFilteredAndSortedFileOperations } from "./GlobalSearchHooks";
|
||||
import type { Datasource } from "entities/Datasource";
|
||||
import { SEARCH_ITEM_TYPES } from "./utils";
|
||||
|
||||
describe("getFilteredAndSortedFileOperations", () => {
|
||||
it("works without any datasources", () => {
|
||||
const fileOptions = getFilteredAndSortedFileOperations("");
|
||||
|
||||
expect(fileOptions[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New JS Object",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New Blank API",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[2]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New Blank GraphQL API",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[3]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New cURL Import",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[4]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New Datasource",
|
||||
}),
|
||||
);
|
||||
});
|
||||
it("works without permissions", () => {
|
||||
const actionOperationsWithoutCreate = getFilteredAndSortedFileOperations(
|
||||
"",
|
||||
[],
|
||||
[],
|
||||
{},
|
||||
false,
|
||||
);
|
||||
|
||||
expect(actionOperationsWithoutCreate.length).toEqual(0);
|
||||
|
||||
const actionOperationsWithoutDatasourcePermission =
|
||||
getFilteredAndSortedFileOperations("", [], [], {}, true, false);
|
||||
|
||||
expect(actionOperationsWithoutDatasourcePermission.length).toEqual(4);
|
||||
});
|
||||
|
||||
it("shows app datasources before other datasources", () => {
|
||||
const appDatasource: Datasource = {
|
||||
datasourceConfiguration: {
|
||||
url: "",
|
||||
},
|
||||
id: "",
|
||||
isValid: true,
|
||||
pluginId: "",
|
||||
workspaceId: "",
|
||||
name: "App datasource",
|
||||
};
|
||||
|
||||
const otherDatasource: Datasource = {
|
||||
datasourceConfiguration: {
|
||||
url: "",
|
||||
},
|
||||
id: "",
|
||||
isValid: false,
|
||||
pluginId: "",
|
||||
workspaceId: "",
|
||||
name: "Other datasource",
|
||||
};
|
||||
|
||||
const fileOptions = getFilteredAndSortedFileOperations(
|
||||
"",
|
||||
[appDatasource],
|
||||
[otherDatasource],
|
||||
{},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(fileOptions[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New JS Object",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "CREATE A QUERY",
|
||||
kind: SEARCH_ITEM_TYPES.sectionTitle,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[2]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New App datasource query",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[3]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New Other datasource query",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("sorts datasources based on recency", () => {
|
||||
const appDatasource: Datasource = {
|
||||
datasourceConfiguration: {
|
||||
url: "",
|
||||
},
|
||||
id: "123",
|
||||
isValid: true,
|
||||
pluginId: "",
|
||||
workspaceId: "",
|
||||
name: "App datasource",
|
||||
};
|
||||
|
||||
const otherDatasource: Datasource = {
|
||||
datasourceConfiguration: {
|
||||
url: "",
|
||||
},
|
||||
id: "abc",
|
||||
isValid: false,
|
||||
pluginId: "",
|
||||
workspaceId: "",
|
||||
name: "Other datasource",
|
||||
};
|
||||
|
||||
const fileOptions = getFilteredAndSortedFileOperations(
|
||||
"",
|
||||
[appDatasource],
|
||||
[otherDatasource],
|
||||
{ abc: 1, "123": 3 },
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(fileOptions[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New JS Object",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[1]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "CREATE A QUERY",
|
||||
kind: SEARCH_ITEM_TYPES.sectionTitle,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[2]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New Other datasource query",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(fileOptions[3]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New App datasource query",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("filters with a query", () => {
|
||||
const appDatasource: Datasource = {
|
||||
datasourceConfiguration: {
|
||||
url: "",
|
||||
},
|
||||
id: "",
|
||||
isValid: true,
|
||||
pluginId: "",
|
||||
workspaceId: "",
|
||||
name: "App datasource",
|
||||
};
|
||||
|
||||
const otherDatasource: Datasource = {
|
||||
datasourceConfiguration: {
|
||||
url: "",
|
||||
},
|
||||
id: "",
|
||||
isValid: false,
|
||||
pluginId: "",
|
||||
workspaceId: "",
|
||||
name: "Other datasource",
|
||||
};
|
||||
|
||||
const fileOptions = getFilteredAndSortedFileOperations(
|
||||
"App",
|
||||
[appDatasource],
|
||||
[otherDatasource],
|
||||
{},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(fileOptions[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New App datasource query",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("Non matching query shows on datasource creation", () => {
|
||||
const appDatasource: Datasource = {
|
||||
datasourceConfiguration: {
|
||||
url: "",
|
||||
},
|
||||
id: "",
|
||||
isValid: true,
|
||||
pluginId: "",
|
||||
workspaceId: "",
|
||||
name: "App datasource",
|
||||
};
|
||||
|
||||
const otherDatasource: Datasource = {
|
||||
datasourceConfiguration: {
|
||||
url: "",
|
||||
},
|
||||
id: "",
|
||||
isValid: false,
|
||||
pluginId: "",
|
||||
workspaceId: "",
|
||||
name: "Other datasource",
|
||||
};
|
||||
|
||||
const fileOptions = getFilteredAndSortedFileOperations(
|
||||
"zzzz",
|
||||
[appDatasource],
|
||||
[otherDatasource],
|
||||
{},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(fileOptions[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "New Datasource",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -10,10 +10,12 @@ import {
|
|||
getAllPageWidgets,
|
||||
getJSCollections,
|
||||
getPlugins,
|
||||
getRecentDatasourceIds,
|
||||
} from "selectors/entitiesSelector";
|
||||
import { useSelector } from "react-redux";
|
||||
import type { EventLocation } from "utils/AnalyticsUtil";
|
||||
import history from "utils/history";
|
||||
import type { ActionOperation } from "./utils";
|
||||
import {
|
||||
actionOperations,
|
||||
attachKind,
|
||||
|
|
@ -35,9 +37,18 @@ import { getCurrentAppWorkspace } from "@appsmith/selectors/workspaceSelectors";
|
|||
|
||||
export const useFilteredFileOperations = (query = "") => {
|
||||
const { appWideDS = [], otherDS = [] } = useAppWideAndOtherDatasource();
|
||||
const recentDatasourceIds = useSelector(getRecentDatasourceIds);
|
||||
// helper map for sorting based on recent usage
|
||||
const recentlyUsedOrderMap = recentDatasourceIds.reduce(
|
||||
(map: Record<string, number>, id, index) => {
|
||||
map[id] = index;
|
||||
return map;
|
||||
},
|
||||
{},
|
||||
);
|
||||
/**
|
||||
* Work around to get the rest api cloud image.
|
||||
* We don't have it store as an svg
|
||||
* We don't have it store as a svg
|
||||
*/
|
||||
const plugins = useSelector(getPlugins);
|
||||
const restApiPlugin = plugins.find(
|
||||
|
|
@ -62,101 +73,118 @@ export const useFilteredFileOperations = (query = "") => {
|
|||
userWorkspacePermissions,
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
let fileOperations: any =
|
||||
(canCreateActions &&
|
||||
actionOperations.filter((op) =>
|
||||
op.title.toLowerCase().includes(query.toLowerCase()),
|
||||
)) ||
|
||||
[];
|
||||
const filteredAppWideDS = appWideDS.filter((ds: Datasource) =>
|
||||
ds.name.toLowerCase().includes(query.toLowerCase()),
|
||||
);
|
||||
const otherFilteredDS = otherDS.filter((ds: Datasource) =>
|
||||
ds.name.toLowerCase().includes(query.toLowerCase()),
|
||||
return useMemo(
|
||||
() =>
|
||||
getFilteredAndSortedFileOperations(
|
||||
query,
|
||||
appWideDS,
|
||||
otherDS,
|
||||
recentlyUsedOrderMap,
|
||||
canCreateActions,
|
||||
canCreateDatasource,
|
||||
pagePermissions,
|
||||
),
|
||||
[query, appWideDS, otherDS],
|
||||
);
|
||||
};
|
||||
|
||||
export const getFilteredAndSortedFileOperations = (
|
||||
query: string,
|
||||
appWideDS: Datasource[] = [],
|
||||
otherDS: Datasource[] = [],
|
||||
recentlyUsedOrderMap: Record<string, number> = {},
|
||||
canCreateActions = true,
|
||||
canCreateDatasource = true,
|
||||
pagePermissions: string[] = [],
|
||||
) => {
|
||||
const fileOperations: ActionOperation[] = [];
|
||||
if (!canCreateActions) return fileOperations;
|
||||
|
||||
// Add JS object operation
|
||||
fileOperations.push(actionOperations[2]);
|
||||
// Add app datasources
|
||||
if (appWideDS.length > 0 || otherDS.length > 0) {
|
||||
const showCreateQuery = [...appWideDS, ...otherDS].some((ds: Datasource) =>
|
||||
hasCreateDatasourceActionPermission([
|
||||
...(ds.userPermissions ?? []),
|
||||
...pagePermissions,
|
||||
]),
|
||||
);
|
||||
|
||||
if (filteredAppWideDS.length > 0 || otherFilteredDS.length > 0) {
|
||||
const showCreateQuery = [...filteredAppWideDS, ...otherFilteredDS].some(
|
||||
(ds: Datasource) =>
|
||||
hasCreateDatasourceActionPermission([
|
||||
...(ds.userPermissions ?? []),
|
||||
...pagePermissions,
|
||||
]),
|
||||
);
|
||||
if (showCreateQuery) {
|
||||
fileOperations.push({
|
||||
desc: "",
|
||||
title: "CREATE A QUERY",
|
||||
kind: SEARCH_ITEM_TYPES.sectionTitle,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fileOperations = [
|
||||
...fileOperations,
|
||||
showCreateQuery && {
|
||||
title: "CREATE A QUERY",
|
||||
kind: SEARCH_ITEM_TYPES.sectionTitle,
|
||||
},
|
||||
];
|
||||
// get all datasources, app ds listed first
|
||||
const datasources = [...appWideDS, ...otherDS].filter((ds) =>
|
||||
hasCreateDatasourceActionPermission([
|
||||
...(ds.userPermissions ?? []),
|
||||
...pagePermissions,
|
||||
]),
|
||||
);
|
||||
|
||||
// Sort datasources based on recency
|
||||
datasources.sort((a, b) => {
|
||||
const orderA = recentlyUsedOrderMap[a.id];
|
||||
const orderB = recentlyUsedOrderMap[b.id];
|
||||
if (orderA !== undefined && orderB !== undefined) {
|
||||
return orderA - orderB;
|
||||
} else if (orderA !== undefined) {
|
||||
return -1;
|
||||
} else if (orderB !== undefined) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
if (filteredAppWideDS.length > 0) {
|
||||
fileOperations = [
|
||||
...fileOperations,
|
||||
...filteredAppWideDS.map((ds) => {
|
||||
return hasCreateDatasourceActionPermission([
|
||||
...(ds.userPermissions ?? []),
|
||||
...pagePermissions,
|
||||
])
|
||||
? {
|
||||
title: `New ${ds.name} Query`,
|
||||
shortTitle: `${ds.name} Query`,
|
||||
desc: `Create a query in ${ds.name}`,
|
||||
pluginId: ds.pluginId,
|
||||
kind: SEARCH_ITEM_TYPES.actionOperation,
|
||||
action: (pageId: string, from: EventLocation) =>
|
||||
createNewQueryAction(pageId, from, ds.id),
|
||||
}
|
||||
: null;
|
||||
}),
|
||||
];
|
||||
}
|
||||
if (otherFilteredDS.length > 0) {
|
||||
fileOperations = [
|
||||
...fileOperations,
|
||||
...otherFilteredDS.map((ds) => {
|
||||
return hasCreateDatasourceActionPermission([
|
||||
...(ds.userPermissions ?? []),
|
||||
...pagePermissions,
|
||||
])
|
||||
? {
|
||||
title: `New ${ds.name} Query`,
|
||||
shortTitle: `${ds.name} Query`,
|
||||
desc: `Create a query in ${ds.name}`,
|
||||
kind: SEARCH_ITEM_TYPES.actionOperation,
|
||||
pluginId: ds.pluginId,
|
||||
action: (pageId: string, from: EventLocation) =>
|
||||
createNewQueryAction(pageId, from, ds.id),
|
||||
}
|
||||
: null;
|
||||
}),
|
||||
];
|
||||
}
|
||||
fileOperations = [
|
||||
...fileOperations,
|
||||
canCreateDatasource && {
|
||||
title: "New Datasource",
|
||||
icon: (
|
||||
<EntityIcon>
|
||||
<AddLineIcon size={22} />
|
||||
</EntityIcon>
|
||||
),
|
||||
kind: SEARCH_ITEM_TYPES.actionOperation,
|
||||
redirect: (pageId: string) => {
|
||||
history.push(
|
||||
integrationEditorURL({
|
||||
pageId,
|
||||
selectedTab: INTEGRATION_TABS.NEW,
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// map into operations
|
||||
const dsOperations = datasources.map((ds) => ({
|
||||
title: `New ${ds.name} query`,
|
||||
shortTitle: `${ds.name} query`,
|
||||
desc: `Create a query in ${ds.name}`,
|
||||
pluginId: ds.pluginId,
|
||||
kind: SEARCH_ITEM_TYPES.actionOperation,
|
||||
action: (pageId: string, from: EventLocation) =>
|
||||
createNewQueryAction(pageId, from, ds.id),
|
||||
}));
|
||||
fileOperations.push(...dsOperations);
|
||||
|
||||
// Add generic action creation
|
||||
fileOperations.push(
|
||||
...actionOperations.filter((op) => op.title !== actionOperations[2].title),
|
||||
);
|
||||
// Filter out based on query
|
||||
const filteredFileOperations = fileOperations
|
||||
.filter(Boolean)
|
||||
.filter((ds) => ds.title.toLowerCase().includes(query.toLowerCase()));
|
||||
// Add genetic datasource creation
|
||||
if (canCreateDatasource) {
|
||||
filteredFileOperations.push({
|
||||
desc: "Create a new datasource in the organisation",
|
||||
title: "New Datasource",
|
||||
icon: (
|
||||
<EntityIcon>
|
||||
<AddLineIcon size={22} />
|
||||
</EntityIcon>
|
||||
),
|
||||
kind: SEARCH_ITEM_TYPES.actionOperation,
|
||||
redirect: (pageId: string) => {
|
||||
history.push(
|
||||
integrationEditorURL({
|
||||
pageId,
|
||||
selectedTab: INTEGRATION_TABS.NEW,
|
||||
}),
|
||||
);
|
||||
},
|
||||
];
|
||||
return fileOperations.filter(Boolean);
|
||||
}, [query, appWideDS, otherDS]);
|
||||
});
|
||||
}
|
||||
return filteredFileOperations;
|
||||
};
|
||||
|
||||
export const useFilteredWidgets = (query: string) => {
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ export default function ExplorerSubMenu({
|
|||
autoComplete="off"
|
||||
autoFocus
|
||||
className="flex-grow text-sm py-2 text-gray-800 bg-transparent placeholder-trueGray-500"
|
||||
data-testId="t--search-file-operation"
|
||||
onChange={onChange}
|
||||
placeholder="Search datasources"
|
||||
type="text"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export interface DatasourceDataState {
|
|||
isFetchingSheets: boolean;
|
||||
isFetchingColumns: boolean;
|
||||
};
|
||||
recentDatasources: string[];
|
||||
}
|
||||
|
||||
const initialState: DatasourceDataState = {
|
||||
|
|
@ -65,6 +66,7 @@ const initialState: DatasourceDataState = {
|
|||
isFetchingSheets: false,
|
||||
isFetchingColumns: false,
|
||||
},
|
||||
recentDatasources: [],
|
||||
};
|
||||
|
||||
const datasourceReducer = createReducer(initialState, {
|
||||
|
|
@ -271,6 +273,7 @@ const datasourceReducer = createReducer(initialState, {
|
|||
list: state.list.concat(action.payload),
|
||||
isDatasourceBeingSaved: false,
|
||||
isDatasourceBeingSavedFromPopup: false,
|
||||
recentDatasources: [action.payload.id, ...state.recentDatasources],
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_DATASOURCE_SUCCESS]: (
|
||||
|
|
@ -290,6 +293,10 @@ const datasourceReducer = createReducer(initialState, {
|
|||
|
||||
return datasource;
|
||||
}),
|
||||
recentDatasources: [
|
||||
action.payload.id,
|
||||
...state.recentDatasources.filter((ds) => ds !== action.payload.id),
|
||||
],
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_DATASOURCE_IMPORT_SUCCESS]: (
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ export const getDatasources = (state: AppState): Datasource[] => {
|
|||
return state.entities.datasources.list;
|
||||
};
|
||||
|
||||
export const getRecentDatasourceIds = (state: AppState): string[] => {
|
||||
return state.entities.datasources.recentDatasources;
|
||||
};
|
||||
|
||||
export const getDatasourcesStructure = (
|
||||
state: AppState,
|
||||
): Record<string, DatasourceStructure> => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user