fix: Reset templates filter for templates modal (#24192)
## Description * Currently we do not reset the template filters when we close template modal and open it again in `add page from template flow` This becomes confusing for some users. * Also increases test coverage of templates filtering #### PR fixes following issue(s) Fixes #17276 #### Media https://github.com/appsmithorg/appsmith/assets/6761673/3c94e21b-e8a9-4c6b-bc81-e677269bb5ea #### Type of change - Bug fix (non-breaking change which fixes an issue) ## Testing > #### How Has This Been Tested? - [x] Cypress #### Test Plan > Add Testsmith test cases links that relate to this PR > > #### Issues raised during DP testing > Link issues raised during DP testing for better visiblity and tracking (copy link from comments dropped on this PR) > > > ## Checklist: #### Dev activity - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] 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: - [ ] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Test-plan-implementation#speedbreaker-features-to-consider-for-every-change) have been covered - [ ] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans/_edit#areas-of-interest) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [ ] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [ ] Cypress test cases have been added and approved by SDET/manual QA - [ ] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
parent
a8b0d5491c
commit
8b912bff5d
|
|
@ -0,0 +1,27 @@
|
|||
import {
|
||||
agHelper,
|
||||
entityExplorer,
|
||||
templates,
|
||||
} from "../../../../../support/Objects/ObjectsCore";
|
||||
|
||||
describe("excludeForAirgap", "Bug 17276 - Templates modal filtering", () => {
|
||||
const NAME_FILTER = "order";
|
||||
it("1. should not retain filters when trying to add a page from template(multiple attempts)", () => {
|
||||
entityExplorer.AddNewPage("Add page from template");
|
||||
agHelper.AssertElementVisible(templates.locators.templateDialogBox);
|
||||
|
||||
agHelper.GetText(templates.locators._resultsHeader).then((headerText) => {
|
||||
templates.FilterTemplatesByName(NAME_FILTER);
|
||||
if (typeof headerText === "string") {
|
||||
templates.AssertResultsHeaderText(headerText, "not.have.text");
|
||||
}
|
||||
agHelper.GetNClick(templates.locators._closeTemplateDialogBoxBtn);
|
||||
|
||||
entityExplorer.AddNewPage("Add page from template");
|
||||
agHelper.AssertElementVisible(templates.locators.templateDialogBox);
|
||||
if (typeof headerText === "string") {
|
||||
templates.AssertResultsHeaderText(headerText, "have.text");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
homePage,
|
||||
agHelper,
|
||||
templates,
|
||||
} from "../../../../../support/Objects/ObjectsCore";
|
||||
|
||||
describe("excludeForAirgap", "Templates page filtering", () => {
|
||||
const FUNCTIONS_FILTER = ["Operations", "Customer Support"];
|
||||
const NAME_FILTER = "order";
|
||||
|
||||
before(() => {
|
||||
homePage.NavigateToHome();
|
||||
templates.SwitchToTemplatesTab();
|
||||
});
|
||||
|
||||
it("1. should filter templates by name", () => {
|
||||
templates.RefreshTemplatesPage(true);
|
||||
templates.FilterTemplatesByName(NAME_FILTER);
|
||||
templates.AssertResultsHeaderText("Showing all 2 templates", "have.text");
|
||||
});
|
||||
|
||||
it("2. should filter templates by functions", () => {
|
||||
templates.RefreshTemplatesPage(true);
|
||||
FUNCTIONS_FILTER.map((func) =>
|
||||
agHelper.CheckUncheck(`input[type='checkbox'][name='${func}']`, true),
|
||||
);
|
||||
|
||||
templates.AssertResultsHeaderText(
|
||||
"Showing all 2 templates matching 2 filters",
|
||||
"have.text",
|
||||
);
|
||||
});
|
||||
|
||||
it("3. should reset filters when coming back from template detailed view", () => {
|
||||
templates.RefreshTemplatesPage(false);
|
||||
|
||||
agHelper
|
||||
.GetText(templates.locators._resultsHeader, "text")
|
||||
.then((headerText) => {
|
||||
templates.FilterTemplatesByName(NAME_FILTER);
|
||||
agHelper.Sleep();
|
||||
agHelper.GetNClick(templates.locators._templateCard);
|
||||
agHelper.GetNClick(templates.locators._templateViewGoBack);
|
||||
agHelper.AssertText(
|
||||
templates.locators._templatesSearchInput,
|
||||
"val",
|
||||
"",
|
||||
);
|
||||
if (typeof headerText === "string") {
|
||||
templates.AssertResultsHeaderText(headerText, "have.text");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -118,7 +118,7 @@
|
|||
"SWITCH_WIDGET",
|
||||
"TABS_WIDGET"
|
||||
],
|
||||
"functions": [],
|
||||
"functions": ["Customer Support"],
|
||||
"useCases": ["Sales"],
|
||||
"datasources": ["mongo-plugin"],
|
||||
"pages": [
|
||||
|
|
|
|||
|
|
@ -830,7 +830,10 @@ export class AggregateHelper extends ReusableHelper {
|
|||
cy.wait(timeout);
|
||||
}
|
||||
|
||||
public RefreshPage(reloadWithoutCache = true, networkCall = "getWorkspace") {
|
||||
public RefreshPage(
|
||||
reloadWithoutCache = true,
|
||||
networkCallAlias = "getWorkspace",
|
||||
) {
|
||||
this.Sleep(2000);
|
||||
this.assertHelper.AssertDocumentReady();
|
||||
// cy.window()
|
||||
|
|
@ -841,7 +844,7 @@ export class AggregateHelper extends ReusableHelper {
|
|||
this.assertHelper.AssertDocumentReady();
|
||||
});
|
||||
this.Sleep(2000);
|
||||
this.assertHelper.AssertNetworkStatus("@" + networkCall); //getWorkspace for Edit page!
|
||||
this.assertHelper.AssertNetworkStatus("@" + networkCallAlias); //getWorkspace for Edit page!
|
||||
}
|
||||
|
||||
public ActionContextMenuWithInPane({
|
||||
|
|
|
|||
|
|
@ -8,14 +8,30 @@ export class Templates {
|
|||
_templatesTab: ".t--templates-tab",
|
||||
_forkApp: ".t--fork-template",
|
||||
_templateCard: "[data-testid='template-card']",
|
||||
_templatesSearchInput: "[data-testid='t--application-search-input']",
|
||||
_resultsHeader: "[data-testid='t--application-templates-results-header']",
|
||||
_templateViewGoBack: "[data-testid='t--template-view-goback']",
|
||||
templateDialogBox: "[data-testid=t--templates-dialog-component]",
|
||||
_closeTemplateDialogBoxBtn: ".ads-v2-modal__content-header-close-button",
|
||||
_requestForTemplateBtn: "span:contains('Request for a template')",
|
||||
};
|
||||
|
||||
ForkTemplateByName(name: string) {
|
||||
cy.contains(this.locators._templateCard, name)
|
||||
.find(this.locators._forkApp)
|
||||
.click();
|
||||
FilterTemplatesByName(query: string) {
|
||||
return ObjectsRegistry.AggregateHelper.TypeText(
|
||||
this.locators._templatesSearchInput,
|
||||
query,
|
||||
);
|
||||
}
|
||||
|
||||
AssertResultsHeaderText(
|
||||
text: string,
|
||||
textPresence: "have.text" | "contain.text" | "not.have.text" = "have.text",
|
||||
) {
|
||||
ObjectsRegistry.AggregateHelper.GetNAssertElementText(
|
||||
this.locators._resultsHeader,
|
||||
text,
|
||||
textPresence,
|
||||
);
|
||||
}
|
||||
|
||||
GetTemplatesCardsList() {
|
||||
|
|
@ -23,12 +39,39 @@ export class Templates {
|
|||
}
|
||||
|
||||
public SwitchToTemplatesTab() {
|
||||
this.homePage.NavigateToHome();
|
||||
this.agHelper.GetNClick(this.locators._templatesTab);
|
||||
this.agHelper.AssertElementVisible(
|
||||
this.locators._requestForTemplateBtn,
|
||||
0,
|
||||
30000,
|
||||
); //giving more time here for templates page to fully load, since there is no intercept validation for same
|
||||
cy.url().then((url) => {
|
||||
if (!url.endsWith("applications")) {
|
||||
this.homePage.NavigateToHome();
|
||||
}
|
||||
this.agHelper.GetNClick(this.locators._templatesTab);
|
||||
this.agHelper.AssertElementVisible(
|
||||
this.locators._requestForTemplateBtn,
|
||||
0,
|
||||
30000,
|
||||
); //giving more time here for templates page to fully load, since there is no intercept validation for same
|
||||
});
|
||||
}
|
||||
|
||||
RefreshTemplatesPage(
|
||||
withDummyData: boolean,
|
||||
templateFixture = "Templates/AllowPageImportTemplates.json",
|
||||
) {
|
||||
if (withDummyData) {
|
||||
cy.fixture(templateFixture).then((templatesData) => {
|
||||
cy.intercept(
|
||||
{
|
||||
method: "GET",
|
||||
url: "/api/v1/app-templates",
|
||||
},
|
||||
{
|
||||
statusCode: 200,
|
||||
body: templatesData,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
cy.intercept("GET", "/api/v1/app-templates/filters").as("fetchFilters");
|
||||
this.agHelper.RefreshPage(false, "fetchFilters");
|
||||
this.agHelper.AssertElementVisible(this.locators._templateCard);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -696,6 +696,7 @@ const ActionTypes = {
|
|||
GET_ALL_TEMPLATES_INIT: "GET_ALL_TEMPLATES_INIT",
|
||||
GET_ALL_TEMPLATES_SUCCESS: "GET_ALL_TEMPLATES_SUCCESS",
|
||||
UPDATE_TEMPLATE_FILTERS: "UPDATE_TEMPLATE_FILTERS",
|
||||
RESET_TEMPLATE_FILTERS: "RESET_TEMPLATE_FILTERS",
|
||||
SET_TEMPLATE_SEARCH_QUERY: "SET_TEMPLATE_SEARCH_QUERY",
|
||||
IMPORT_TEMPLATE_TO_APPLICATION_INIT: "IMPORT_TEMPLATE_TO_APPLICATION_INIT",
|
||||
IMPORT_TEMPLATE_TO_APPLICATION_SUCCESS:
|
||||
|
|
|
|||
|
|
@ -95,7 +95,8 @@ function FilterItem({ item, onSelect, selected }: FilterItemProps) {
|
|||
<Checkbox
|
||||
// backgroundColor={Colors.GREY_900}
|
||||
// className="filter"
|
||||
defaultSelected={selected}
|
||||
isSelected={selected}
|
||||
name={item.label}
|
||||
onChange={onClick}
|
||||
value={item.label}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,11 @@ function TemplateViewHeader({ templateId }: Props) {
|
|||
};
|
||||
return (
|
||||
<HeaderWrapper>
|
||||
<Link onClick={goBack} startIcon="arrow-left-line">
|
||||
<Link
|
||||
data-testid="t--template-view-goback"
|
||||
onClick={goBack}
|
||||
startIcon="arrow-left-line"
|
||||
>
|
||||
{createMessage(GO_BACK)}
|
||||
</Link>
|
||||
<Title kind="heading-l" renderAs="h1">
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
|
|||
import styled from "styled-components";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
allTemplatesFiltersSelector,
|
||||
templateModalOpenSelector,
|
||||
templatesCountSelector,
|
||||
} from "selectors/templatesSelectors";
|
||||
|
|
@ -17,6 +18,7 @@ import { isEmpty } from "lodash";
|
|||
import type { AppState } from "@appsmith/reducers";
|
||||
import { Modal, ModalBody, ModalContent, ModalHeader } from "design-system";
|
||||
import TemplateModalHeader from "./Header";
|
||||
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
|
||||
|
||||
const ModalContentWrapper = styled(ModalContent)`
|
||||
width: 100%;
|
||||
|
|
@ -33,13 +35,16 @@ function TemplatesModal() {
|
|||
const pluginListLength = useSelector(
|
||||
(state: AppState) => state.entities.plugins.defaultPluginList.length,
|
||||
);
|
||||
const filters = useSelector(
|
||||
(state: AppState) => state.ui.templates.allFilters,
|
||||
);
|
||||
const filters = useSelector(allTemplatesFiltersSelector);
|
||||
const [showTemplateDetails, setShowTemplateDetails] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setShowTemplateDetails("");
|
||||
if (templatesModalOpen) {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.RESET_TEMPLATE_FILTERS,
|
||||
});
|
||||
}
|
||||
}, [templatesModalOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { debounce, noop, isEmpty } from "lodash";
|
||||
import { isEmpty } from "lodash";
|
||||
import { Switch, Route, useRouteMatch } from "react-router-dom";
|
||||
import { SearchInput, Text } from "design-system";
|
||||
import TemplateList from "./TemplateList";
|
||||
|
|
@ -15,6 +15,7 @@ import {
|
|||
setTemplateSearchQuery,
|
||||
} from "actions/templateActions";
|
||||
import {
|
||||
allTemplatesFiltersSelector,
|
||||
getForkableWorkspaces,
|
||||
getSearchedTemplateList,
|
||||
getTemplateFiltersLength,
|
||||
|
|
@ -34,6 +35,8 @@ import LeftPaneBottomSection from "@appsmith/pages/Home/LeftPaneBottomSection";
|
|||
import type { Template } from "api/TemplatesApi";
|
||||
import LoadingScreen from "./TemplatesModal/LoadingScreen";
|
||||
import ReconnectDatasourceModal from "pages/Editor/gitSync/ReconnectDatasourceModal";
|
||||
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
|
||||
|
||||
const SentryRoute = Sentry.withSentryRouting(Route);
|
||||
|
||||
const PageWrapper = styled.div`
|
||||
|
|
@ -102,9 +105,7 @@ function TemplateRoutes() {
|
|||
const templatesCount = useSelector(
|
||||
(state: AppState) => state.ui.templates.templates.length,
|
||||
);
|
||||
const filters = useSelector(
|
||||
(state: AppState) => state.ui.templates.allFilters,
|
||||
);
|
||||
const filters = useSelector(allTemplatesFiltersSelector);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setHeaderMeta(true, true));
|
||||
|
|
@ -161,13 +162,17 @@ export function TemplatesContent(props: TemplatesContentProps) {
|
|||
const onChange = (query: string) => {
|
||||
dispatch(setTemplateSearchQuery(query));
|
||||
};
|
||||
const debouncedOnChange = debounce(onChange, 250, { maxWait: 1000 });
|
||||
const filterWithAllowPageImport = props.filterWithAllowPageImport || false;
|
||||
const templates = useSelector(getSearchedTemplateList).filter((template) =>
|
||||
filterWithAllowPageImport ? !!template.allowPageImport : true,
|
||||
);
|
||||
const filterCount = useSelector(getTemplateFiltersLength);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.RESET_TEMPLATE_FILTERS,
|
||||
});
|
||||
}, []);
|
||||
let resultsText =
|
||||
templates.length > 1
|
||||
? `Showing all ${templates.length} templates`
|
||||
|
|
@ -195,13 +200,17 @@ export function TemplatesContent(props: TemplatesContentProps) {
|
|||
<SearchInput
|
||||
data-testid={"t--application-search-input"}
|
||||
isDisabled={isLoading}
|
||||
onChange={debouncedOnChange || noop}
|
||||
onChange={onChange}
|
||||
placeholder={createMessage(SEARCH_TEMPLATES)}
|
||||
value={templateSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
</SearchWrapper>
|
||||
<ResultsCount kind="heading-m" renderAs="h1">
|
||||
<ResultsCount
|
||||
data-testid="t--application-templates-results-header"
|
||||
kind="heading-m"
|
||||
renderAs="h1"
|
||||
>
|
||||
{resultsText}
|
||||
</ResultsCount>
|
||||
<TemplateList
|
||||
|
|
|
|||
|
|
@ -69,6 +69,15 @@ const templateReducer = createReducer(initialState, {
|
|||
},
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.RESET_TEMPLATE_FILTERS]: (
|
||||
state: TemplatesReduxState,
|
||||
): TemplatesReduxState => {
|
||||
return {
|
||||
...state,
|
||||
filters: {},
|
||||
templateSearchQuery: "",
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.SET_TEMPLATE_SEARCH_QUERY]: (
|
||||
state: TemplatesReduxState,
|
||||
action: ReduxAction<string>,
|
||||
|
|
|
|||
|
|
@ -142,14 +142,14 @@ export const templatesDatasourceFiltersSelector = createSelector(
|
|||
},
|
||||
);
|
||||
|
||||
export const templatesFiltersSelector = (state: AppState) =>
|
||||
export const allTemplatesFiltersSelector = (state: AppState) =>
|
||||
state.ui.templates.allFilters;
|
||||
|
||||
// Get all filters which is associated with atleast one template
|
||||
// If no template is associated with a filter, then the filter shouldn't be in the filter list
|
||||
export const getFilterListSelector = createSelector(
|
||||
getTemplatesSelector,
|
||||
templatesFiltersSelector,
|
||||
allTemplatesFiltersSelector,
|
||||
(templates, allTemplateFilters) => {
|
||||
const FUNCTIONS_FILTER = "functions";
|
||||
const filters: Record<string, Filter[]> = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user