Feat: Share application publicly (#89)

* Feat:  Share application publically

* fix: eslint warnings and code refactor
This commit is contained in:
Tejaaswini Narendra 2020-07-15 15:17:39 +05:30 committed by GitHub
parent b1e8e83e5d
commit 2956f1b3d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 224 additions and 6 deletions

View File

@ -6,6 +6,11 @@ export interface PublishApplicationRequest {
applicationId: string;
}
export interface ChangeAppViewAccessRequest {
applicationId: string;
publicAccess: boolean;
}
export interface PublishApplicationResponse extends ApiResponse {
data: {};
}
@ -79,6 +84,8 @@ class ApplicationApi extends Api {
static baseURL = "v1/applications/";
static publishURLPath = (applicationId: string) => `publish/${applicationId}`;
static createApplicationPath = (orgId: string) => `?orgId=${orgId}`;
static changeAppViewAccessPath = (applicationId: string) =>
`${applicationId}/changeAccess`;
static setDefaultPagePath = (request: SetDefaultPageRequest) =>
`${ApplicationApi.baseURL}${request.applicationId}/page/${request.pageId}/makeDefault`;
static publishApplication(
@ -120,6 +127,17 @@ class ApplicationApi extends Api {
): AxiosPromise<ApiResponse> {
return Api.put(ApplicationApi.setDefaultPagePath(request));
}
static changeAppViewAccess(
request: ChangeAppViewAccessRequest,
): AxiosPromise<ApiResponse> {
return Api.put(
ApplicationApi.baseURL +
ApplicationApi.changeAppViewAccessPath(request.applicationId),
{ publicAccess: request.publicAccess },
);
}
static deleteApplication(
request: DeleteApplicationRequest,
): AxiosPromise<ApiResponse> {

View File

@ -82,6 +82,9 @@ export const ReduxActionTypes: { [key: string]: string } = {
STORE_AS_DATASOURCE_COMPLETE: "STORE_AS_DATASOURCE_COMPLETE",
PUBLISH_APPLICATION_INIT: "PUBLISH_APPLICATION_INIT",
PUBLISH_APPLICATION_SUCCESS: "PUBLISH_APPLICATION_SUCCESS",
CHANGE_APPVIEW_ACCESS_INIT: "CHANGE_APPVIEW_ACCESS_INIT",
CHANGE_APPVIEW_ACCESS_SUCCESS: "CHANGE_APPVIEW_ACCESS_SUCCESS",
CHANGE_APPVIEW_ACCESS_ERROR: "CHANGE_APPVIEW_ACCESS_ERROR",
CREATE_PAGE_INIT: "CREATE_PAGE_INIT",
CREATE_PAGE_SUCCESS: "CREATE_PAGE_SUCCESS",
FETCH_PAGE_LIST_INIT: "FETCH_PAGE_LIST_INIT",
@ -382,6 +385,7 @@ export type ApplicationPayload = {
organizationId: string;
pageCount: number;
defaultPageId?: string;
isPublic?: boolean;
userPermissions?: string[];
};

View File

@ -1,6 +1,14 @@
import React from "react";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { AppState } from "reducers";
import styled from "styled-components";
import { Breadcrumbs, IBreadcrumbProps } from "@blueprintjs/core";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import {
getCurrentOrg,
getCurrentOrgId,
} from "selectors/organizationSelectors";
import { Org } from "constants/orgConstants";
import {
BASE_URL,
APPLICATIONS_URL,
@ -22,6 +30,8 @@ import {
import AnalyticsUtil from "utils/AnalyticsUtil";
import { Skin } from "constants/DefaultTheme";
import { HelpModal } from "components/designSystems/appsmith/help/HelpModal";
import { FormDialogComponent } from "components/editorComponents/form/FormDialogComponent";
import ShareApplicationForm from "pages/Editor/ShareApplicationForm";
const LoadingContainer = styled.div`
display: flex;
@ -48,13 +58,16 @@ const StretchedBreadCrumb = styled(Breadcrumbs)`
}
`;
const InviteButton = styled.div`
const ShareButton = styled.div`
display: flex;
flex-grow: 1;
justify-content: flex-end;
`;
type EditorHeaderProps = {
currentOrg: Org;
currentOrgId: string;
fetchCurrentOrg: (orgId: string) => void;
isSaving?: boolean;
pageSaveError?: boolean;
pageName?: string;
@ -77,6 +90,12 @@ export const EditorHeader = (props: EditorHeaderProps) => {
page => page.pageId === props.currentPageId,
)?.pageName;
const { fetchCurrentOrg, currentOrgId } = props;
useEffect(() => {
fetchCurrentOrg(currentOrgId);
}, [fetchCurrentOrg, currentOrgId]);
const pageSelectorData: CustomizedDropdownProps = {
sections: [
{
@ -146,9 +165,22 @@ export const EditorHeader = (props: EditorHeaderProps) => {
<StyledHeader>
<StretchedBreadCrumb items={navigation} minVisibleItems={3} />
<CustomizedDropdown {...pageSelectorData} />
<InviteButton>
{/* <Button text="Share" intent="primary" filled size="small" /> */}
</InviteButton>
<ShareButton>
<FormDialogComponent
trigger={
<Button
text="Share"
intent="primary"
outline
size="small"
className="t--application-publish-btn"
/>
}
Form={ShareApplicationForm}
title={props.currentOrg.name}
/>
</ShareButton>
<LoadingContainer>{saveStatusMessage}</LoadingContainer>
<PreviewPublishSection>
<Button
@ -166,4 +198,19 @@ export const EditorHeader = (props: EditorHeaderProps) => {
);
};
export default EditorHeader;
const mapStateToProps = (state: AppState) => ({
currentOrg: getCurrentOrg(state),
currentOrgId: getCurrentOrgId(state),
});
const mapDispatchToProps = (dispatch: any) => ({
fetchCurrentOrg: (orgId: string) =>
dispatch({
type: ReduxActionTypes.FETCH_CURRENT_ORG,
payload: {
orgId,
},
}),
});
export default connect(mapStateToProps, mapDispatchToProps)(EditorHeader);

View File

@ -0,0 +1,94 @@
import React, { useEffect } from "react";
import styled from "styled-components";
import { withRouter } from "react-router";
import { connect } from "react-redux";
import { AppState } from "reducers";
import { StyledSwitch } from "components/propertyControls/StyledControls";
import { fetchApplication } from "actions/applicationActions";
import Spinner from "components/editorComponents/Spinner";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
const ShareWithPublicOption = styled.div`
{
display: flex;
padding-top: 20px;
justify-content: space-between;
}
`;
const ShareToggle = styled.div`
{
&&& label {
margin-bottom: 0px;
}
&&& div {
margin-right: 5px;
}
display: flex;
}
`;
export const ShareApplicationForm = (props: any) => {
const {
match: {
params: { applicationId },
},
fetchApplication,
isFetchingApplication,
isChangingViewAccess,
currentApplicationDetails,
changeAppViewAccess,
} = props;
useEffect(() => {
fetchApplication(applicationId);
}, [fetchApplication, applicationId]);
return (
<ShareWithPublicOption>
Share the application with anyone
<ShareToggle>
{(isChangingViewAccess || isFetchingApplication) && (
<Spinner size={20} />
)}
{currentApplicationDetails && (
<StyledSwitch
onChange={() => {
changeAppViewAccess(
applicationId,
!currentApplicationDetails.isPublic,
);
}}
disabled={isChangingViewAccess || isFetchingApplication}
checked={currentApplicationDetails.isPublic}
large
/>
)}
</ShareToggle>
</ShareWithPublicOption>
);
};
const mapStateToProps = (state: AppState) => ({
currentApplicationDetails: state.ui.applications.currentApplication,
isFetchingApplication: state.ui.applications.isFetchingApplication,
isChangingViewAccess: state.ui.applications.isChangingViewAccess,
});
const mapDispatchToProps = (dispatch: any) => ({
fetchApplication: (applicationId: string) => {
return dispatch(fetchApplication(applicationId));
},
changeAppViewAccess: (applicationId: string, publicAccess: boolean) =>
dispatch({
type: ReduxActionTypes.CHANGE_APPVIEW_ACCESS_INIT,
payload: {
applicationId,
publicAccess,
},
}),
});
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(ShareApplicationForm),
);

View File

@ -10,6 +10,8 @@ import { ERROR_MESSAGE_CREATE_APPLICATION } from "constants/messages";
const initialState: ApplicationsReduxState = {
isFetchingApplications: false,
isFetchingApplication: false,
isChangingViewAccess: false,
applicationList: [],
creatingApplication: false,
deletingApplication: false,
@ -56,6 +58,22 @@ const applicationsReducer = createReducer(initialState, {
) => {
return { ...state, deletingApplication: false };
},
[ReduxActionTypes.CHANGE_APPVIEW_ACCESS_INIT]: (
state: ApplicationsReduxState,
) => ({ ...state, isChangingViewAccess: true }),
[ReduxActionTypes.CHANGE_APPVIEW_ACCESS_SUCCESS]: (
state: ApplicationsReduxState,
action: ReduxAction<{ id: string; isPublic: boolean }>,
) => {
return {
...state,
isChangingViewAccess: false,
currentApplication: {
...state.currentApplication,
isPublic: action.payload.isPublic,
},
};
},
[ReduxActionTypes.FETCH_APPLICATION_LIST_INIT]: (
state: ApplicationsReduxState,
) => ({ ...state, isFetchingApplications: true }),
@ -127,6 +145,8 @@ export interface ApplicationsReduxState {
applicationList: ApplicationPayload[];
searchKeyword?: string;
isFetchingApplications: boolean;
isFetchingApplication: boolean;
isChangingViewAccess: boolean;
creatingApplication: boolean;
createApplicationError?: string;
deletingApplication: boolean;

View File

@ -17,6 +17,7 @@ import ApplicationApi, {
FetchUsersApplicationsOrgsResponse,
OrganizationApplicationObject,
ApplicationObject,
ChangeAppViewAccessRequest,
} from "api/ApplicationApi";
import { getDefaultPageId } from "./SagaUtils";
import { call, put, takeLatest, all, select } from "redux-saga/effects";
@ -29,6 +30,7 @@ import { BUILDER_PAGE_URL } from "constants/routes";
import { AppState } from "reducers";
import { setDefaultApplicationPageSuccess } from "actions/applicationActions";
import AnalyticsUtil from "utils/AnalyticsUtil";
export function* publishApplicationSaga(
requestAction: ReduxAction<PublishApplicationRequest>,
) {
@ -214,6 +216,35 @@ export function* deleteApplicationSaga(
}
}
export function* changeAppViewAccessSaga(
requestAction: ReduxAction<ChangeAppViewAccessRequest>,
) {
try {
const request = requestAction.payload;
const response: ApiResponse = yield call(
ApplicationApi.changeAppViewAccess,
request,
);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
yield put({
type: ReduxActionTypes.CHANGE_APPVIEW_ACCESS_SUCCESS,
payload: {
id: response.data.id,
isPublic: response.data.isPublic,
},
});
}
} catch (error) {
yield put({
type: ReduxActionErrorTypes.CHANGE_APPVIEW_ACCESS_ERROR,
payload: {
error,
},
});
}
}
export function* createApplicationSaga(
action: ReduxAction<{
applicationName: string;
@ -299,6 +330,10 @@ export default function* applicationSagas() {
ReduxActionTypes.FETCH_APPLICATION_LIST_INIT,
fetchApplicationListSaga,
),
takeLatest(
ReduxActionTypes.CHANGE_APPVIEW_ACCESS_INIT,
changeAppViewAccessSaga,
),
takeLatest(
ReduxActionTypes.GET_ALL_APPLICATION_INIT,
getAllApplicationSaga,