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",
|
||||
"popover": "//div[contains(@class,'t--entity page')]//*[local-name()='g' and @id='Icon/Outline/more-vertical']",
|
||||
"editName": ".single-select >div:contains('Edit Name')",
|
||||
"clonePage": ".single-select >div:contains('Clone')",
|
||||
"deletePage": ".single-select >div:contains('Delete')",
|
||||
"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("POST", "/api/v1/pages").as("createPage");
|
||||
cy.route("POST", "/api/v1/pages/clone/*").as("clonePage");
|
||||
});
|
||||
|
||||
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) => {
|
||||
return {
|
||||
type: ReduxActionTypes.UPDATE_PAGE_INIT,
|
||||
|
|
|
|||
|
|
@ -78,6 +78,10 @@ export interface DeletePageRequest {
|
|||
id: string;
|
||||
}
|
||||
|
||||
export interface ClonePageRequest {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface UpdateWidgetNameRequest {
|
||||
pageId: string;
|
||||
layoutId: string;
|
||||
|
|
@ -150,6 +154,10 @@ class PageApi extends Api {
|
|||
return Api.delete(PageApi.url + "/" + request.id);
|
||||
}
|
||||
|
||||
static clonePage(request: ClonePageRequest): AxiosPromise<ApiResponse> {
|
||||
return Api.post(PageApi.url + "/clone/" + request.id);
|
||||
}
|
||||
|
||||
static updateWidgetName(
|
||||
request: UpdateWidgetNameRequest,
|
||||
): AxiosPromise<UpdateWidgetNameResponse> {
|
||||
|
|
|
|||
|
|
@ -161,6 +161,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
DELETE_APPLICATION_SUCCESS: "DELETE_APPLICATION_SUCCESS",
|
||||
DELETE_PAGE_INIT: "DELETE_PAGE_INIT",
|
||||
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_SUCCESS: "SET_DEFAULT_APPLICATION_PAGE_SUCCESS",
|
||||
CREATE_ORGANIZATION_INIT: "CREATE_ORGANIZATION_INIT",
|
||||
|
|
@ -312,6 +314,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
|||
MOVE_ACTION_ERROR: "MOVE_ACTION_ERROR",
|
||||
COPY_ACTION_ERROR: "COPY_ACTION_ERROR",
|
||||
DELETE_PAGE_ERROR: "DELETE_PAGE_ERROR",
|
||||
CLONE_PAGE_ERROR: "CLONE_PAGE_ERROR",
|
||||
DELETE_APPLICATION_ERROR: "DELETE_APPLICATION_ERROR",
|
||||
SET_DEFAULT_APPLICATION_PAGE_ERROR: "SET_DEFAULT_APPLICATION_PAGE_ERROR",
|
||||
CREATE_ORGANIZATION_ERROR: "CREATE_ORGANIZATION_ERROR",
|
||||
|
|
@ -394,6 +397,13 @@ export interface Page {
|
|||
latest?: boolean;
|
||||
}
|
||||
|
||||
export interface ClonePageSuccessPayload {
|
||||
pageName: string;
|
||||
pageId: string;
|
||||
layoutId: string;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
export type PageListPayload = Array<Page>;
|
||||
|
||||
export type ApplicationPayload = {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
|||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { ContextMenuPopoverModifiers } from "../helpers";
|
||||
import { initExplorerEntityNameEdit } from "actions/explorerActions";
|
||||
import { clonePageInit } from "actions/pageActions";
|
||||
|
||||
export const PageContextMenu = (props: {
|
||||
pageId: string;
|
||||
|
|
@ -52,12 +53,22 @@ export const PageContextMenu = (props: {
|
|||
[dispatch, props.pageId],
|
||||
);
|
||||
|
||||
const clonePage = useCallback(() => dispatch(clonePageInit(props.pageId)), [
|
||||
dispatch,
|
||||
props.pageId,
|
||||
]);
|
||||
|
||||
const optionTree: TreeDropdownOption[] = [
|
||||
{
|
||||
value: "rename",
|
||||
onSelect: editPageName,
|
||||
label: "Edit Name",
|
||||
},
|
||||
{
|
||||
value: "clone",
|
||||
onSelect: clonePage,
|
||||
label: "Clone",
|
||||
},
|
||||
];
|
||||
if (!props.isDefaultPage) {
|
||||
optionTree.push({
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
ReduxAction,
|
||||
ReduxActionTypes,
|
||||
PageListPayload,
|
||||
ClonePageSuccessPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
|
||||
const initialState: PageListReduxState = {
|
||||
|
|
@ -51,6 +52,17 @@ const pageListReducer = createReducer(initialState, {
|
|||
_state.pages.push({ ...action.payload, latest: true });
|
||||
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]: (
|
||||
state: PageListReduxState,
|
||||
action: ReduxAction<{ pageId: string; applicationId: string }>,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ const initialState: EditorReduxState = {
|
|||
isPageSwitching: false,
|
||||
creatingPage: false,
|
||||
creatingPageError: false,
|
||||
cloningPage: false,
|
||||
cloningPageError: false,
|
||||
updatingWidgetName: false,
|
||||
updateWidgetNameError: false,
|
||||
},
|
||||
|
|
@ -111,6 +113,20 @@ const editorReducer = createReducer(initialState, {
|
|||
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) => {
|
||||
state.loadingStates.creatingPage = true;
|
||||
state.loadingStates.creatingPageError = false;
|
||||
|
|
@ -162,6 +178,8 @@ export interface EditorReduxState {
|
|||
pageSwitchingError: boolean;
|
||||
creatingPage: boolean;
|
||||
creatingPageError: boolean;
|
||||
cloningPage: boolean;
|
||||
cloningPageError: boolean;
|
||||
updatingWidgetName: boolean;
|
||||
updateWidgetNameError: boolean;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ const explorerReducer = createReducer(initialState, {
|
|||
[ReduxActionTypes.FETCH_PAGE_ERROR]: setEntityUpdateError,
|
||||
[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,
|
||||
[ReduxActionErrorTypes.MOVE_ACTION_ERROR]: setEntityUpdateError,
|
||||
[ReduxActionTypes.MOVE_ACTION_SUCCESS]: setEntityUpdateSuccess,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from "constants/ReduxActionConstants";
|
||||
import {
|
||||
deletePageSuccess,
|
||||
clonePageSuccess,
|
||||
fetchPageSuccess,
|
||||
fetchPublishedPageSuccess,
|
||||
savePageSuccess,
|
||||
|
|
@ -31,6 +32,7 @@ import PageApi, {
|
|||
UpdatePageRequest,
|
||||
UpdateWidgetNameRequest,
|
||||
UpdateWidgetNameResponse,
|
||||
ClonePageRequest,
|
||||
} from "api/PageApi";
|
||||
import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
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(
|
||||
action: ReduxAction<{ id: string; newName: string }>,
|
||||
) {
|
||||
|
|
@ -477,6 +513,7 @@ export default function* pageSagas() {
|
|||
),
|
||||
takeLatest(ReduxActionTypes.UPDATE_LAYOUT, saveLayoutSaga),
|
||||
takeLeading(ReduxActionTypes.CREATE_PAGE_INIT, createPageSaga),
|
||||
takeLeading(ReduxActionTypes.CLONE_PAGE_INIT, clonePageSaga),
|
||||
takeLatest(ReduxActionTypes.FETCH_PAGE_LIST_INIT, fetchPageListSaga),
|
||||
takeLatest(ReduxActionTypes.UPDATE_PAGE_INIT, updatePageSaga),
|
||||
takeLatest(ReduxActionTypes.DELETE_PAGE_INIT, deletePageSaga),
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user