Merge branch 'release' of https://github.com/appsmithorg/appsmith into release

This commit is contained in:
Trisha Anand 2020-07-15 15:23:09 +05:30
commit f2b10d5759
15 changed files with 553 additions and 82 deletions

View File

@ -67,10 +67,7 @@ axiosInstance.interceptors.response.use(
} }
} }
const errorData = error.response.data.responseMeta; const errorData = error.response.data.responseMeta;
if ( if (errorData.status === 404 && errorData.error.code === 4028) {
errorData.status === 404 &&
errorData.error.code === 4028
) {
history.push(PAGE_NOT_FOUND_URL); history.push(PAGE_NOT_FOUND_URL);
return Promise.reject({ return Promise.reject({
code: 404, code: 404,

View File

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

View File

@ -82,6 +82,9 @@ export const ReduxActionTypes: { [key: string]: string } = {
STORE_AS_DATASOURCE_COMPLETE: "STORE_AS_DATASOURCE_COMPLETE", STORE_AS_DATASOURCE_COMPLETE: "STORE_AS_DATASOURCE_COMPLETE",
PUBLISH_APPLICATION_INIT: "PUBLISH_APPLICATION_INIT", PUBLISH_APPLICATION_INIT: "PUBLISH_APPLICATION_INIT",
PUBLISH_APPLICATION_SUCCESS: "PUBLISH_APPLICATION_SUCCESS", 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_INIT: "CREATE_PAGE_INIT",
CREATE_PAGE_SUCCESS: "CREATE_PAGE_SUCCESS", CREATE_PAGE_SUCCESS: "CREATE_PAGE_SUCCESS",
FETCH_PAGE_LIST_INIT: "FETCH_PAGE_LIST_INIT", FETCH_PAGE_LIST_INIT: "FETCH_PAGE_LIST_INIT",
@ -382,6 +385,7 @@ export type ApplicationPayload = {
organizationId: string; organizationId: string;
pageCount: number; pageCount: number;
defaultPageId?: string; defaultPageId?: string;
isPublic?: boolean;
userPermissions?: string[]; 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 styled from "styled-components";
import { Breadcrumbs, IBreadcrumbProps } from "@blueprintjs/core"; import { Breadcrumbs, IBreadcrumbProps } from "@blueprintjs/core";
import { ReduxActionTypes } from "constants/ReduxActionConstants";
import {
getCurrentOrg,
getCurrentOrgId,
} from "selectors/organizationSelectors";
import { Org } from "constants/orgConstants";
import { import {
BASE_URL, BASE_URL,
APPLICATIONS_URL, APPLICATIONS_URL,
@ -22,6 +30,8 @@ import {
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { Skin } from "constants/DefaultTheme"; import { Skin } from "constants/DefaultTheme";
import { HelpModal } from "components/designSystems/appsmith/help/HelpModal"; 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` const LoadingContainer = styled.div`
display: flex; display: flex;
@ -48,13 +58,16 @@ const StretchedBreadCrumb = styled(Breadcrumbs)`
} }
`; `;
const InviteButton = styled.div` const ShareButton = styled.div`
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
justify-content: flex-end; justify-content: flex-end;
`; `;
type EditorHeaderProps = { type EditorHeaderProps = {
currentOrg: Org;
currentOrgId: string;
fetchCurrentOrg: (orgId: string) => void;
isSaving?: boolean; isSaving?: boolean;
pageSaveError?: boolean; pageSaveError?: boolean;
pageName?: string; pageName?: string;
@ -77,6 +90,12 @@ export const EditorHeader = (props: EditorHeaderProps) => {
page => page.pageId === props.currentPageId, page => page.pageId === props.currentPageId,
)?.pageName; )?.pageName;
const { fetchCurrentOrg, currentOrgId } = props;
useEffect(() => {
fetchCurrentOrg(currentOrgId);
}, [fetchCurrentOrg, currentOrgId]);
const pageSelectorData: CustomizedDropdownProps = { const pageSelectorData: CustomizedDropdownProps = {
sections: [ sections: [
{ {
@ -146,9 +165,22 @@ export const EditorHeader = (props: EditorHeaderProps) => {
<StyledHeader> <StyledHeader>
<StretchedBreadCrumb items={navigation} minVisibleItems={3} /> <StretchedBreadCrumb items={navigation} minVisibleItems={3} />
<CustomizedDropdown {...pageSelectorData} /> <CustomizedDropdown {...pageSelectorData} />
<InviteButton> <ShareButton>
{/* <Button text="Share" intent="primary" filled size="small" /> */} <FormDialogComponent
</InviteButton> trigger={
<Button
text="Share"
intent="primary"
outline
size="small"
className="t--application-publish-btn"
/>
}
Form={ShareApplicationForm}
title={props.currentOrg.name}
/>
</ShareButton>
<LoadingContainer>{saveStatusMessage}</LoadingContainer> <LoadingContainer>{saveStatusMessage}</LoadingContainer>
<PreviewPublishSection> <PreviewPublishSection>
<Button <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 = { const initialState: ApplicationsReduxState = {
isFetchingApplications: false, isFetchingApplications: false,
isFetchingApplication: false,
isChangingViewAccess: false,
applicationList: [], applicationList: [],
creatingApplication: false, creatingApplication: false,
deletingApplication: false, deletingApplication: false,
@ -56,6 +58,22 @@ const applicationsReducer = createReducer(initialState, {
) => { ) => {
return { ...state, deletingApplication: false }; 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]: ( [ReduxActionTypes.FETCH_APPLICATION_LIST_INIT]: (
state: ApplicationsReduxState, state: ApplicationsReduxState,
) => ({ ...state, isFetchingApplications: true }), ) => ({ ...state, isFetchingApplications: true }),
@ -127,6 +145,8 @@ export interface ApplicationsReduxState {
applicationList: ApplicationPayload[]; applicationList: ApplicationPayload[];
searchKeyword?: string; searchKeyword?: string;
isFetchingApplications: boolean; isFetchingApplications: boolean;
isFetchingApplication: boolean;
isChangingViewAccess: boolean;
creatingApplication: boolean; creatingApplication: boolean;
createApplicationError?: string; createApplicationError?: string;
deletingApplication: boolean; deletingApplication: boolean;

View File

@ -17,6 +17,7 @@ import ApplicationApi, {
FetchUsersApplicationsOrgsResponse, FetchUsersApplicationsOrgsResponse,
OrganizationApplicationObject, OrganizationApplicationObject,
ApplicationObject, ApplicationObject,
ChangeAppViewAccessRequest,
} from "api/ApplicationApi"; } from "api/ApplicationApi";
import { getDefaultPageId } from "./SagaUtils"; import { getDefaultPageId } from "./SagaUtils";
import { call, put, takeLatest, all, select } from "redux-saga/effects"; 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 { AppState } from "reducers";
import { setDefaultApplicationPageSuccess } from "actions/applicationActions"; import { setDefaultApplicationPageSuccess } from "actions/applicationActions";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
export function* publishApplicationSaga( export function* publishApplicationSaga(
requestAction: ReduxAction<PublishApplicationRequest>, 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( export function* createApplicationSaga(
action: ReduxAction<{ action: ReduxAction<{
applicationName: string; applicationName: string;
@ -299,6 +330,10 @@ export default function* applicationSagas() {
ReduxActionTypes.FETCH_APPLICATION_LIST_INIT, ReduxActionTypes.FETCH_APPLICATION_LIST_INIT,
fetchApplicationListSaga, fetchApplicationListSaga,
), ),
takeLatest(
ReduxActionTypes.CHANGE_APPVIEW_ACCESS_INIT,
changeAppViewAccessSaga,
),
takeLatest( takeLatest(
ReduxActionTypes.GET_ALL_APPLICATION_INIT, ReduxActionTypes.GET_ALL_APPLICATION_INIT,
getAllApplicationSaga, getAllApplicationSaga,

View File

@ -129,6 +129,10 @@ export function* deleteDatasourceSaga(
history.push(DATA_SOURCES_EDITOR_URL(applicationId, pageId)); history.push(DATA_SOURCES_EDITOR_URL(applicationId, pageId));
} }
} catch (error) { } catch (error) {
AppToaster.show({
message: error.message,
type: ToastType.ERROR,
});
yield put({ yield put({
type: ReduxActionErrorTypes.DELETE_DATASOURCE_ERROR, type: ReduxActionErrorTypes.DELETE_DATASOURCE_ERROR,
payload: { error, id: actionPayload.payload.id }, payload: { error, id: actionPayload.payload.id },

View File

@ -59,7 +59,7 @@ export function getResponseErrorMessage(response: ApiResponse) {
: undefined; : undefined;
} }
type ErrorPayloadType = { code?: number; message?: string, show?:boolean }; type ErrorPayloadType = { code?: number; message?: string; show?: boolean };
let ActionErrorDisplayMap: { let ActionErrorDisplayMap: {
[key: string]: (error: ErrorPayloadType) => string; [key: string]: (error: ErrorPayloadType) => string;
} = {}; } = {};

View File

@ -0,0 +1,165 @@
import TernServer from "./TernServer";
import { MockCodemirrorEditor } from "../../../test/__mocks__/CodeMirrorEditorMock";
jest.mock("jsExecution/RealmExecutor");
describe("Tern server", () => {
it("Check whether the correct value is being sent to tern", () => {
const ternServer = new TernServer({});
const testCases = [
{
input: {
name: "test",
doc: ({
getCursor: () => ({ ch: 0, line: 0 }),
getLine: () => "{{Api.}}",
} as unknown) as CodeMirror.Doc,
changed: null,
},
expectedOutput: "{{Api.}}",
},
{
input: {
name: "test",
doc: ({
getCursor: () => ({ ch: 0, line: 0 }),
getLine: () => "a{{Api.}}",
} as unknown) as CodeMirror.Doc,
changed: null,
},
expectedOutput: "a{{Api.}}",
},
{
input: {
name: "test",
doc: ({
getCursor: () => ({ ch: 2, line: 0 }),
getLine: () => "a{{Api.}}",
} as unknown) as CodeMirror.Doc,
changed: null,
},
expectedOutput: "{{Api.}}",
},
];
testCases.forEach(testCase => {
const value = ternServer.getFocusedDynamicValue(testCase.input);
expect(value).toBe(testCase.expectedOutput);
});
});
it("Check whether the correct position is sent for querying autocomplete", () => {
const ternServer = new TernServer({});
const testCases = [
{
input: {
name: "test",
doc: ({
getCursor: () => ({ ch: 0, line: 0 }),
getLine: () => "{{Api.}}",
somethingSelected: () => false,
} as unknown) as CodeMirror.Doc,
changed: null,
},
expectedOutput: { ch: 0, line: 0 },
},
{
input: {
name: "test",
doc: ({
getCursor: () => ({ ch: 0, line: 1 }),
getLine: () => "{{Api.}}",
somethingSelected: () => false,
} as unknown) as CodeMirror.Doc,
changed: null,
},
expectedOutput: { ch: 0, line: 0 },
},
{
input: {
name: "test",
doc: ({
getCursor: () => ({ ch: 3, line: 1 }),
getLine: () => "g {{Api.}}",
somethingSelected: () => false,
} as unknown) as CodeMirror.Doc,
changed: null,
},
expectedOutput: { ch: 1, line: 0 },
},
];
testCases.forEach((testCase, index) => {
const request = ternServer.buildRequest(testCase.input, {});
expect(request.query.end).toEqual(testCase.expectedOutput);
});
});
it(`Check whether the position is evaluated correctly for placing the selected
autocomplete value`, () => {
const ternServer = new TernServer({});
const testCases = [
{
input: {
codeEditor: {
value: "{{}}",
cursor: { ch: 2, line: 0 },
doc: ({
getCursor: () => ({ ch: 2, line: 0 }),
getLine: () => "{{}}",
somethingSelected: () => false,
} as unknown) as CodeMirror.Doc,
},
requestCallbackData: {
completions: [{ name: "Api1" }],
start: { ch: 2, line: 0 },
end: { ch: 6, line: 0 },
},
},
expectedOutput: { ch: 2, line: 0 },
},
{
input: {
codeEditor: {
value: "\n {{}}",
cursor: { ch: 3, line: 1 },
doc: ({
getCursor: () => ({ ch: 3, line: 1 }),
getLine: () => " {{}}",
somethingSelected: () => false,
} as unknown) as CodeMirror.Doc,
},
requestCallbackData: {
completions: [{ name: "Api1" }],
start: { ch: 2, line: 1 },
end: { ch: 6, line: 1 },
},
},
expectedOutput: { ch: 3, line: 1 },
},
];
testCases.forEach((testCase, index) => {
MockCodemirrorEditor.getValue.mockReturnValueOnce(
testCase.input.codeEditor.value,
);
MockCodemirrorEditor.getCursor.mockReturnValueOnce(
testCase.input.codeEditor.cursor,
);
MockCodemirrorEditor.getDoc.mockReturnValueOnce(
testCase.input.codeEditor.doc,
);
const value: any = ternServer.requestCallback(
null,
testCase.input.requestCallbackData,
(MockCodemirrorEditor as unknown) as CodeMirror.Editor,
() => null,
);
expect(value.from).toEqual(testCase.expectedOutput);
});
});
});

View File

@ -6,6 +6,10 @@ import ecma from "tern/defs/ecmascript.json";
import lodash from "constants/defs/lodash.json"; import lodash from "constants/defs/lodash.json";
import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator"; import { dataTreeTypeDefCreator } from "utils/autocomplete/dataTreeTypeDefCreator";
import CodeMirror, { Hint, Pos, cmpPos } from "codemirror"; import CodeMirror, { Hint, Pos, cmpPos } from "codemirror";
import {
getDynamicStringSegments,
isDynamicValue,
} from "utils/DynamicBindingUtils";
const DEFS = [ecma, lodash]; const DEFS = [ecma, lodash];
const bigDoc = 250; const bigDoc = 250;
@ -70,27 +74,34 @@ class TernServer {
this.server.addDefs(def, true); this.server.addDefs(def, true);
} }
getHint(cm: CodeMirror.Editor) { requestCallback(
return new Promise(resolve => { error: any,
this.request( data: any,
cm, cm: CodeMirror.Editor,
{ resolve: Function,
type: "completions", ) {
types: true,
docs: true,
urls: true,
origins: true,
caseInsensitive: true,
},
(error, data) => {
if (error) return this.showError(cm, error); if (error) return this.showError(cm, error);
if (data.completions.length === 0) { if (data.completions.length === 0) {
return this.showError(cm, "No suggestions"); return this.showError(cm, "No suggestions");
} }
const doc = this.findDoc(cm.getDoc());
const cursor = cm.getCursor();
const lineValue = this.lineValue(doc);
const focusedValue = this.getFocusedDynamicValue(doc);
const index = lineValue.indexOf(focusedValue);
let completions: Completion[] = []; let completions: Completion[] = [];
let after = ""; let after = "";
const from = data.start; const { start, end } = data;
const to = data.end; const from = {
...start,
ch: start.ch + index,
line: cursor.line,
};
const to = {
...end,
ch: end.ch + index,
line: cursor.line,
};
if ( if (
cm.getRange(Pos(from.line, from.ch - 2), from) === '["' && cm.getRange(Pos(from.line, from.ch - 2), from) === '["' &&
cm.getRange(to, Pos(to.line, to.ch + 2)) !== '"]' cm.getRange(to, Pos(to.line, to.ch + 2)) !== '"]'
@ -123,8 +134,7 @@ class TernServer {
const content = cur.data.doc; const content = cur.data.doc;
if (content) { if (content) {
tooltip = this.makeTooltip( tooltip = this.makeTooltip(
node.parentNode.getBoundingClientRect().right + node.parentNode.getBoundingClientRect().right + window.pageXOffset,
window.pageXOffset,
node.getBoundingClientRect().top + window.pageYOffset, node.getBoundingClientRect().top + window.pageYOffset,
content, content,
); );
@ -133,7 +143,23 @@ class TernServer {
}, },
); );
resolve(obj); resolve(obj);
return obj;
}
getHint(cm: CodeMirror.Editor) {
return new Promise(resolve => {
this.request(
cm,
{
type: "completions",
types: true,
docs: true,
urls: true,
origins: true,
caseInsensitive: true,
}, },
(error, data) => this.requestCallback(error, data, cm, resolve),
); );
}); });
} }
@ -231,7 +257,7 @@ class TernServer {
addDoc(name: string, doc: CodeMirror.Doc) { addDoc(name: string, doc: CodeMirror.Doc) {
const data = { doc: doc, name: name, changed: null }; const data = { doc: doc, name: name, changed: null };
this.server.addFile(name, this.docValue(data)); this.server.addFile(name, this.getFocusedDynamicValue(data));
CodeMirror.on(doc, "change", this.trackChange.bind(this)); CodeMirror.on(doc, "change", this.trackChange.bind(this));
return (this.docs[name] = data); return (this.docs[name] = data);
} }
@ -258,7 +284,19 @@ class TernServer {
if (!allowFragments) delete query.fullDocs; if (!allowFragments) delete query.fullDocs;
query.lineCharPositions = true; query.lineCharPositions = true;
if (!query.end) { if (!query.end) {
query.end = pos || doc.doc.getCursor("end"); const lineValue = this.lineValue(doc);
const focusedValue = this.getFocusedDynamicValue(doc);
const index = lineValue.indexOf(focusedValue);
const positions = pos || doc.doc.getCursor("end");
const queryChPosition = positions.ch - index;
query.end = {
...positions,
line: 0,
ch: queryChPosition,
};
if (doc.doc.somethingSelected()) { if (doc.doc.somethingSelected()) {
query.start = doc.doc.getCursor("start"); query.start = doc.doc.getCursor("start");
} }
@ -283,7 +321,7 @@ class TernServer {
files.push({ files.push({
type: "full", type: "full",
name: doc.name, name: doc.name,
text: this.docValue(doc), text: this.getFocusedDynamicValue(doc),
}); });
query.file = doc.name; query.file = doc.name;
doc.changed = null; doc.changed = null;
@ -297,11 +335,12 @@ class TernServer {
files.push({ files.push({
type: "full", type: "full",
name: cur.name, name: cur.name,
text: this.docValue(cur), text: this.getFocusedDynamicValue(cur),
}); });
cur.changed = null; cur.changed = null;
} }
} }
return { query: query, files: files }; return { query: query, files: files };
} }
@ -341,8 +380,17 @@ class TernServer {
sendDoc(doc: TernDoc) { sendDoc(doc: TernDoc) {
this.server.request( this.server.request(
{
// @ts-ignore // @ts-ignore
{ files: [{ type: "full", name: doc.name, text: this.docValue(doc) }] }, files: [
// @ts-ignore
{
type: "full",
name: doc.name,
text: this.getFocusedDynamicValue(doc),
},
],
},
function(error: Error) { function(error: Error) {
if (error) window.console.error(error); if (error) window.console.error(error);
else doc.changed = null; else doc.changed = null;
@ -350,10 +398,35 @@ class TernServer {
); );
} }
lineValue(doc: TernDoc) {
const cursor = doc.doc.getCursor();
return doc.doc.getLine(cursor.line);
}
docValue(doc: TernDoc) { docValue(doc: TernDoc) {
return doc.doc.getValue(); return doc.doc.getValue();
} }
getFocusedDynamicValue(doc: TernDoc) {
const cursor = doc.doc.getCursor();
const value = this.lineValue(doc);
const stringSegments = getDynamicStringSegments(value);
const dynamicStrings = stringSegments.filter(segment => {
if (isDynamicValue(segment)) {
const index = value.indexOf(segment);
if (cursor.ch >= index && cursor.ch <= index + segment.length) {
return true;
}
}
return false;
});
return dynamicStrings.length ? dynamicStrings[0] : value;
}
getFragmentAround( getFragmentAround(
data: TernDoc, data: TernDoc,
start: CodeMirror.Position, start: CodeMirror.Position,

View File

@ -8,4 +8,6 @@ export const MockCodemirrorEditor = {
showHint: jest.fn(), showHint: jest.fn(),
getLine: jest.fn(), getLine: jest.fn(),
closeHint: jest.fn(), closeHint: jest.fn(),
getRange: jest.fn(),
getDoc: jest.fn(),
}; };

View File

@ -12651,8 +12651,9 @@ websocket-driver@>=0.5.1:
websocket-extensions ">=0.1.1" websocket-extensions ">=0.1.1"
websocket-extensions@>=0.1.1: websocket-extensions@>=0.1.1:
version "0.1.3" version "0.1.4"
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
version "1.0.5" version "1.0.5"

View File

@ -130,7 +130,6 @@ if [[ "$setup_encryption" = "true" ]];then
fi fi
fi fi
echo "" echo ""
read -p 'Would you like to host appsmith on a custom domain / subdomain? [Y/n]: ' setup_domain read -p 'Would you like to host appsmith on a custom domain / subdomain? [Y/n]: ' setup_domain
setup_domain=${setup_domain:-Y} setup_domain=${setup_domain:-Y}
if [ $setup_domain == "Y" -o $setup_domain == "y" -o $setup_domain == "yes" -o $setup_domain == "Yes" ];then if [ $setup_domain == "Y" -o $setup_domain == "y" -o $setup_domain == "yes" -o $setup_domain == "Yes" ];then

View File

@ -8,6 +8,10 @@ if [ -f docker-compose.yml ]
fi fi
cat > docker.env << EOF cat > docker.env << EOF
# Read our documentation on how to configure these features
# https://docs.appsmith.com/v/v1.1/enabling-3p-services
# ***** Email **********
APPSMITH_MAIL_ENABLED=false APPSMITH_MAIL_ENABLED=false
# APPSMITH_MAIL_HOST= # APPSMITH_MAIL_HOST=
# APPSMITH_MAIL_PASSWORD= # APPSMITH_MAIL_PASSWORD=
@ -15,16 +19,24 @@ APPSMITH_MAIL_ENABLED=false
# APPSMITH_MAIL_SMTP_AUTH= # APPSMITH_MAIL_SMTP_AUTH=
# APPSMITH_MAIL_SMTP_TLS_ENABLED= # APPSMITH_MAIL_SMTP_TLS_ENABLED=
# APPSMITH_MAIL_USERNAME= # APPSMITH_MAIL_USERNAME=
# APPSMITH_MARKETPLACE_URL= # ******************************
APPSMITH_MONGODB_URI=mongodb://$mongo_root_user:$mongo_root_password@$mongo_host/appsmith?retryWrites=true
# APPSMITH_OAUTH2_GITHUB_CLIENT_ID= # ******** Google OAuth ********
# APPSMITH_OAUTH2_GITHUB_CLIENT_SECRET=
# APPSMITH_OAUTH2_GOOGLE_CLIENT_ID= # APPSMITH_OAUTH2_GOOGLE_CLIENT_ID=
# APPSMITH_OAUTH2_GOOGLE_CLIENT_SECRET= # APPSMITH_OAUTH2_GOOGLE_CLIENT_SECRET=
# APPSMITH_RAPID_API_KEY_VALUE= # ******************************
APPSMITH_REDIS_URL=redis://redis:6379
# APPSMITH_ROLLBAR_ACCESS_TOKEN=
# APPSMITH_ROLLBAR_ENV=
# APPSMITH_SEGMENT_KEY=
# ********* Github OAUth **********
# APPSMITH_OAUTH2_GITHUB_CLIENT_ID=
# APPSMITH_OAUTH2_GITHUB_CLIENT_SECRET=
# *********************************
# ******** Google Maps ***********
# APPSMITH_GOOGLE_MAPS_API_KEY=
# ********************************
# ******** Database *************
APPSMITH_REDIS_URL=redis://redis:6379
APPSMITH_MONGODB_URI=mongodb://$mongo_root_user:$mongo_root_password@$mongo_host/appsmith?retryWrites=true
# *******************************
EOF EOF