Add clone page option (#365)
Adding the option to clone a page in the overflow menu of the entity explorer
This commit is contained in:
parent
27b7f6c5e2
commit
3deb9ace43
|
|
@ -0,0 +1,18 @@
|
||||||
|
const pages = require("../../../locators/Pages.json");
|
||||||
|
|
||||||
|
describe("Pages", function() {
|
||||||
|
it("Clone page", function() {
|
||||||
|
cy.xpath(pages.popover)
|
||||||
|
.last()
|
||||||
|
.click({ force: true });
|
||||||
|
cy.get(pages.clonePage).click({ force: true });
|
||||||
|
|
||||||
|
cy.wait("@clonePage").should(
|
||||||
|
"have.nested.property",
|
||||||
|
"response.body.responseMeta.status",
|
||||||
|
201,
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.get(".t--entity-name:contains(Page1 Copy)");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"entityExplorer": ".t--nav-link-entity-explorer",
|
"entityExplorer": ".t--nav-link-entity-explorer",
|
||||||
"popover": "//div[contains(@class,'t--entity page')]//*[local-name()='g' and @id='Icon/Outline/more-vertical']",
|
"popover": "//div[contains(@class,'t--entity page')]//*[local-name()='g' and @id='Icon/Outline/more-vertical']",
|
||||||
"editName": ".single-select >div:contains('Edit Name')",
|
"editName": ".single-select >div:contains('Edit Name')",
|
||||||
|
"clonePage": ".single-select >div:contains('Clone')",
|
||||||
"deletePage": ".single-select >div:contains('Delete')",
|
"deletePage": ".single-select >div:contains('Delete')",
|
||||||
"entityQuery": ".t--entity-name:contains('Queries')"
|
"entityQuery": ".t--entity-name:contains('Queries')"
|
||||||
}
|
}
|
||||||
|
|
@ -1478,6 +1478,7 @@ Cypress.Commands.add("startServerAndRoutes", () => {
|
||||||
);
|
);
|
||||||
cy.route("GET", "/api/v1/users/me").as("getUser");
|
cy.route("GET", "/api/v1/users/me").as("getUser");
|
||||||
cy.route("POST", "/api/v1/pages").as("createPage");
|
cy.route("POST", "/api/v1/pages").as("createPage");
|
||||||
|
cy.route("POST", "/api/v1/pages/clone/*").as("clonePage");
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add("alertValidate", text => {
|
Cypress.Commands.add("alertValidate", text => {
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,30 @@ export const createPage = (applicationId: string, pageName: string) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const clonePageInit = (pageId: string) => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.CLONE_PAGE_INIT,
|
||||||
|
payload: {
|
||||||
|
id: pageId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clonePageSuccess = (
|
||||||
|
pageId: string,
|
||||||
|
pageName: string,
|
||||||
|
layoutId: string,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
type: ReduxActionTypes.CLONE_PAGE_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
pageId,
|
||||||
|
pageName,
|
||||||
|
layoutId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const updatePage = (id: string, name: string) => {
|
export const updatePage = (id: string, name: string) => {
|
||||||
return {
|
return {
|
||||||
type: ReduxActionTypes.UPDATE_PAGE_INIT,
|
type: ReduxActionTypes.UPDATE_PAGE_INIT,
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,10 @@ export interface DeletePageRequest {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClonePageRequest {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UpdateWidgetNameRequest {
|
export interface UpdateWidgetNameRequest {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
layoutId: string;
|
layoutId: string;
|
||||||
|
|
@ -150,6 +154,10 @@ class PageApi extends Api {
|
||||||
return Api.delete(PageApi.url + "/" + request.id);
|
return Api.delete(PageApi.url + "/" + request.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static clonePage(request: ClonePageRequest): AxiosPromise<ApiResponse> {
|
||||||
|
return Api.post(PageApi.url + "/clone/" + request.id);
|
||||||
|
}
|
||||||
|
|
||||||
static updateWidgetName(
|
static updateWidgetName(
|
||||||
request: UpdateWidgetNameRequest,
|
request: UpdateWidgetNameRequest,
|
||||||
): AxiosPromise<UpdateWidgetNameResponse> {
|
): AxiosPromise<UpdateWidgetNameResponse> {
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
||||||
DELETE_APPLICATION_SUCCESS: "DELETE_APPLICATION_SUCCESS",
|
DELETE_APPLICATION_SUCCESS: "DELETE_APPLICATION_SUCCESS",
|
||||||
DELETE_PAGE_INIT: "DELETE_PAGE_INIT",
|
DELETE_PAGE_INIT: "DELETE_PAGE_INIT",
|
||||||
DELETE_PAGE_SUCCESS: "DELETE_PAGE_SUCCESS",
|
DELETE_PAGE_SUCCESS: "DELETE_PAGE_SUCCESS",
|
||||||
|
CLONE_PAGE_INIT: "CLONE_PAGE_INIT",
|
||||||
|
CLONE_PAGE_SUCCESS: "CLONE_PAGE_SUCCESS",
|
||||||
SET_DEFAULT_APPLICATION_PAGE_INIT: "SET_DEFAULT_APPLICATION_PAGE_INIT",
|
SET_DEFAULT_APPLICATION_PAGE_INIT: "SET_DEFAULT_APPLICATION_PAGE_INIT",
|
||||||
SET_DEFAULT_APPLICATION_PAGE_SUCCESS: "SET_DEFAULT_APPLICATION_PAGE_SUCCESS",
|
SET_DEFAULT_APPLICATION_PAGE_SUCCESS: "SET_DEFAULT_APPLICATION_PAGE_SUCCESS",
|
||||||
CREATE_ORGANIZATION_INIT: "CREATE_ORGANIZATION_INIT",
|
CREATE_ORGANIZATION_INIT: "CREATE_ORGANIZATION_INIT",
|
||||||
|
|
@ -312,6 +314,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
||||||
MOVE_ACTION_ERROR: "MOVE_ACTION_ERROR",
|
MOVE_ACTION_ERROR: "MOVE_ACTION_ERROR",
|
||||||
COPY_ACTION_ERROR: "COPY_ACTION_ERROR",
|
COPY_ACTION_ERROR: "COPY_ACTION_ERROR",
|
||||||
DELETE_PAGE_ERROR: "DELETE_PAGE_ERROR",
|
DELETE_PAGE_ERROR: "DELETE_PAGE_ERROR",
|
||||||
|
CLONE_PAGE_ERROR: "CLONE_PAGE_ERROR",
|
||||||
DELETE_APPLICATION_ERROR: "DELETE_APPLICATION_ERROR",
|
DELETE_APPLICATION_ERROR: "DELETE_APPLICATION_ERROR",
|
||||||
SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR",
|
SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR",
|
||||||
CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR",
|
CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR",
|
||||||
|
|
@ -394,6 +397,13 @@ export interface Page {
|
||||||
latest?: boolean;
|
latest?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ClonePageSuccessPayload {
|
||||||
|
pageName: string;
|
||||||
|
pageId: string;
|
||||||
|
layoutId: string;
|
||||||
|
isDefault: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type PageListPayload = Array<Page>;
|
export type PageListPayload = Array<Page>;
|
||||||
|
|
||||||
export type ApplicationPayload = {
|
export type ApplicationPayload = {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||||
import { ContextMenuPopoverModifiers } from "../helpers";
|
import { ContextMenuPopoverModifiers } from "../helpers";
|
||||||
import { initExplorerEntityNameEdit } from "actions/explorerActions";
|
import { initExplorerEntityNameEdit } from "actions/explorerActions";
|
||||||
|
import { clonePageInit } from "actions/pageActions";
|
||||||
|
|
||||||
export const PageContextMenu = (props: {
|
export const PageContextMenu = (props: {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
|
|
@ -52,12 +53,22 @@ export const PageContextMenu = (props: {
|
||||||
[dispatch, props.pageId],
|
[dispatch, props.pageId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const clonePage = useCallback(() => dispatch(clonePageInit(props.pageId)), [
|
||||||
|
dispatch,
|
||||||
|
props.pageId,
|
||||||
|
]);
|
||||||
|
|
||||||
const optionTree: TreeDropdownOption[] = [
|
const optionTree: TreeDropdownOption[] = [
|
||||||
{
|
{
|
||||||
value: "rename",
|
value: "rename",
|
||||||
onSelect: editPageName,
|
onSelect: editPageName,
|
||||||
label: "Edit Name",
|
label: "Edit Name",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: "clone",
|
||||||
|
onSelect: clonePage,
|
||||||
|
label: "Clone",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
if (!props.isDefaultPage) {
|
if (!props.isDefaultPage) {
|
||||||
optionTree.push({
|
optionTree.push({
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
ReduxAction,
|
ReduxAction,
|
||||||
ReduxActionTypes,
|
ReduxActionTypes,
|
||||||
PageListPayload,
|
PageListPayload,
|
||||||
|
ClonePageSuccessPayload,
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
|
|
||||||
const initialState: PageListReduxState = {
|
const initialState: PageListReduxState = {
|
||||||
|
|
@ -51,6 +52,17 @@ const pageListReducer = createReducer(initialState, {
|
||||||
_state.pages.push({ ...action.payload, latest: true });
|
_state.pages.push({ ...action.payload, latest: true });
|
||||||
return { ..._state };
|
return { ..._state };
|
||||||
},
|
},
|
||||||
|
[ReduxActionTypes.CLONE_PAGE_SUCCESS]: (
|
||||||
|
state: PageListReduxState,
|
||||||
|
action: ReduxAction<ClonePageSuccessPayload>,
|
||||||
|
): PageListReduxState => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
pages: state.pages
|
||||||
|
.map(page => ({ ...page, latest: false }))
|
||||||
|
.concat([{ ...action.payload, latest: true }]),
|
||||||
|
};
|
||||||
|
},
|
||||||
[ReduxActionTypes.SET_DEFAULT_APPLICATION_PAGE_SUCCESS]: (
|
[ReduxActionTypes.SET_DEFAULT_APPLICATION_PAGE_SUCCESS]: (
|
||||||
state: PageListReduxState,
|
state: PageListReduxState,
|
||||||
action: ReduxAction<{ pageId: string; applicationId: string }>,
|
action: ReduxAction<{ pageId: string; applicationId: string }>,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ const initialState: EditorReduxState = {
|
||||||
isPageSwitching: false,
|
isPageSwitching: false,
|
||||||
creatingPage: false,
|
creatingPage: false,
|
||||||
creatingPageError: false,
|
creatingPageError: false,
|
||||||
|
cloningPage: false,
|
||||||
|
cloningPageError: false,
|
||||||
updatingWidgetName: false,
|
updatingWidgetName: false,
|
||||||
updateWidgetNameError: false,
|
updateWidgetNameError: false,
|
||||||
},
|
},
|
||||||
|
|
@ -111,6 +113,20 @@ const editorReducer = createReducer(initialState, {
|
||||||
currentApplicationId,
|
currentApplicationId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
[ReduxActionTypes.CLONE_PAGE_INIT]: (state: EditorReduxState) => {
|
||||||
|
state.loadingStates.cloningPage = true;
|
||||||
|
state.loadingStates.cloningPageError = false;
|
||||||
|
return { ...state };
|
||||||
|
},
|
||||||
|
[ReduxActionTypes.CLONE_PAGE_ERROR]: (state: EditorReduxState) => {
|
||||||
|
state.loadingStates.cloningPageError = true;
|
||||||
|
state.loadingStates.cloningPage = false;
|
||||||
|
return { ...state };
|
||||||
|
},
|
||||||
|
[ReduxActionTypes.CLONE_PAGE_SUCCESS]: (state: EditorReduxState) => {
|
||||||
|
state.loadingStates.cloningPage = false;
|
||||||
|
return { ...state };
|
||||||
|
},
|
||||||
[ReduxActionTypes.CREATE_PAGE_INIT]: (state: EditorReduxState) => {
|
[ReduxActionTypes.CREATE_PAGE_INIT]: (state: EditorReduxState) => {
|
||||||
state.loadingStates.creatingPage = true;
|
state.loadingStates.creatingPage = true;
|
||||||
state.loadingStates.creatingPageError = false;
|
state.loadingStates.creatingPageError = false;
|
||||||
|
|
@ -162,6 +178,8 @@ export interface EditorReduxState {
|
||||||
pageSwitchingError: boolean;
|
pageSwitchingError: boolean;
|
||||||
creatingPage: boolean;
|
creatingPage: boolean;
|
||||||
creatingPageError: boolean;
|
creatingPageError: boolean;
|
||||||
|
cloningPage: boolean;
|
||||||
|
cloningPageError: boolean;
|
||||||
updatingWidgetName: boolean;
|
updatingWidgetName: boolean;
|
||||||
updateWidgetNameError: boolean;
|
updateWidgetNameError: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@ const explorerReducer = createReducer(initialState, {
|
||||||
[ReduxActionTypes.FETCH_PAGE_ERROR]: setEntityUpdateError,
|
[ReduxActionTypes.FETCH_PAGE_ERROR]: setEntityUpdateError,
|
||||||
[ReduxActionTypes.FETCH_PAGE_SUCCESS]: setEntityUpdateSuccess,
|
[ReduxActionTypes.FETCH_PAGE_SUCCESS]: setEntityUpdateSuccess,
|
||||||
|
|
||||||
|
[ReduxActionTypes.CLONE_PAGE_INIT]: setUpdatingEntity,
|
||||||
|
[ReduxActionTypes.CLONE_PAGE_ERROR]: setEntityUpdateError,
|
||||||
|
[ReduxActionTypes.CLONE_PAGE_SUCCESS]: setEntityUpdateSuccess,
|
||||||
|
|
||||||
[ReduxActionTypes.MOVE_ACTION_INIT]: setUpdatingEntity,
|
[ReduxActionTypes.MOVE_ACTION_INIT]: setUpdatingEntity,
|
||||||
[ReduxActionErrorTypes.MOVE_ACTION_ERROR]: setEntityUpdateError,
|
[ReduxActionErrorTypes.MOVE_ACTION_ERROR]: setEntityUpdateError,
|
||||||
[ReduxActionTypes.MOVE_ACTION_SUCCESS]: setEntityUpdateSuccess,
|
[ReduxActionTypes.MOVE_ACTION_SUCCESS]: setEntityUpdateSuccess,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
} from "constants/ReduxActionConstants";
|
} from "constants/ReduxActionConstants";
|
||||||
import {
|
import {
|
||||||
deletePageSuccess,
|
deletePageSuccess,
|
||||||
|
clonePageSuccess,
|
||||||
fetchPageSuccess,
|
fetchPageSuccess,
|
||||||
fetchPublishedPageSuccess,
|
fetchPublishedPageSuccess,
|
||||||
savePageSuccess,
|
savePageSuccess,
|
||||||
|
|
@ -31,6 +32,7 @@ import PageApi, {
|
||||||
UpdatePageRequest,
|
UpdatePageRequest,
|
||||||
UpdateWidgetNameRequest,
|
UpdateWidgetNameRequest,
|
||||||
UpdateWidgetNameResponse,
|
UpdateWidgetNameResponse,
|
||||||
|
ClonePageRequest,
|
||||||
} from "api/PageApi";
|
} from "api/PageApi";
|
||||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||||
import {
|
import {
|
||||||
|
|
@ -382,6 +384,40 @@ export function* deletePageSaga(action: ReduxAction<DeletePageRequest>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function* clonePageSaga(clonePageAction: ReduxAction<ClonePageRequest>) {
|
||||||
|
try {
|
||||||
|
const request: ClonePageRequest = clonePageAction.payload;
|
||||||
|
const response: FetchPageResponse = yield call(PageApi.clonePage, request);
|
||||||
|
const applicationId = yield select(
|
||||||
|
(state: AppState) => state.entities.pageList.applicationId,
|
||||||
|
);
|
||||||
|
const isValidResponse = yield validateResponse(response);
|
||||||
|
if (isValidResponse) {
|
||||||
|
yield put(
|
||||||
|
clonePageSuccess(
|
||||||
|
response.data.id,
|
||||||
|
response.data.name,
|
||||||
|
response.data.layouts[0].id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionTypes.FETCH_PAGE_DSL_INIT,
|
||||||
|
payload: {
|
||||||
|
pageId: response.data.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
history.push(BUILDER_PAGE_URL(applicationId, response.data.id));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
yield put({
|
||||||
|
type: ReduxActionErrorTypes.CLONE_PAGE_ERROR,
|
||||||
|
payload: {
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function* updateWidgetNameSaga(
|
export function* updateWidgetNameSaga(
|
||||||
action: ReduxAction<{ id: string; newName: string }>,
|
action: ReduxAction<{ id: string; newName: string }>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -477,6 +513,7 @@ export default function* pageSagas() {
|
||||||
),
|
),
|
||||||
takeLatest(ReduxActionTypes.UPDATE_LAYOUT, saveLayoutSaga),
|
takeLatest(ReduxActionTypes.UPDATE_LAYOUT, saveLayoutSaga),
|
||||||
takeLeading(ReduxActionTypes.CREATE_PAGE_INIT, createPageSaga),
|
takeLeading(ReduxActionTypes.CREATE_PAGE_INIT, createPageSaga),
|
||||||
|
takeLeading(ReduxActionTypes.CLONE_PAGE_INIT, clonePageSaga),
|
||||||
takeLatest(ReduxActionTypes.FETCH_PAGE_LIST_INIT, fetchPageListSaga),
|
takeLatest(ReduxActionTypes.FETCH_PAGE_LIST_INIT, fetchPageListSaga),
|
||||||
takeLatest(ReduxActionTypes.UPDATE_PAGE_INIT, updatePageSaga),
|
takeLatest(ReduxActionTypes.UPDATE_PAGE_INIT, updatePageSaga),
|
||||||
takeLatest(ReduxActionTypes.DELETE_PAGE_INIT, deletePageSaga),
|
takeLatest(ReduxActionTypes.DELETE_PAGE_INIT, deletePageSaga),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user