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:
Rahul Barwal 2023-06-29 11:52:05 +05:30 committed by GitHub
parent a8b0d5491c
commit 8b912bff5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 184 additions and 28 deletions

View File

@ -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");
}
});
});
});

View File

@ -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");
}
});
});
});

View File

@ -118,7 +118,7 @@
"SWITCH_WIDGET",
"TABS_WIDGET"
],
"functions": [],
"functions": ["Customer Support"],
"useCases": ["Sales"],
"datasources": ["mongo-plugin"],
"pages": [

View File

@ -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({

View File

@ -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);
}
}

View File

@ -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:

View File

@ -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}
>

View File

@ -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">

View File

@ -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(() => {

View File

@ -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

View File

@ -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>,

View File

@ -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[]> = {