Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Zeppelin-3307] - Improved shared browsing/editing for the note #2848

Closed
wants to merge 16 commits into from

Conversation

Savalek
Copy link
Contributor

@Savalek Savalek commented Mar 7, 2018

What is this PR for?

Now if the note is opened in several tabs (or several users are watching or editing it), then there may be problems. Loss of code entered by the user, reset the cursor position.
This PR adds a basic opportunity for collaborative editing. For the organization of joint editing, the library diff-match-patch is used.
PR does not change the logic of operation if the note is used by one person.
Also, maybe this will solve the problem with ZEPPELIN-3131.

What type of PR is it?

Improvement

What is the Jira issue?

ZEPPELIN-3307

Screenshots (if appropriate)

gif

Questions:

  • Does the licenses files need update? no
  • Is there breaking changes for older versions? no
  • Does this needs documentation? no

# Conflicts:
#	zeppelin-server/pom.xml
#	zeppelin-web/src/app/notebook/notebook.controller.js
#	zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
#	zeppelin-web/src/components/websocket/websocket-event.factory.js
#	zeppelin-web/src/components/websocket/websocket-message.service.js
@Savalek
Copy link
Contributor Author

Savalek commented Mar 21, 2018

@prabhjyotsingh @r-kamath
could you help with the review?

@mebelousov
Copy link
Contributor

Zeppelin has websockets and two users can view a note simultaneously, but there is a bug.

  • Open the note in two tabs.
  • Write anything in first tab for second.
  • Wait for 10 seconds.
  • Repeat the last two steps several times.
  • Check that written code is lost.

Thus paired coding is painful.
Please review this PR.

@jongyoul
Copy link
Member

jongyoul commented May 1, 2018

I think this PR might be not bad for a small number of users, but, I'm not sure users increase. What do you think of it? It looks like calling websockets everytime whenever all users make changes

# Conflicts:
#	zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
#	zeppelin-zengine/src/main/java/org/apache/zeppelin/notebook/socket/Message.java
@Savalek
Copy link
Contributor Author

Savalek commented May 3, 2018

@jongyoul, yes sending any change to the server is a big load. But this will happen only if the notebook is opened by several users. The current implementation is subject to errors in consequence of which users may lose some of the code. Even if the second user just open the tab in the browser. This is very annoying.

@zjffdu
Copy link
Contributor

zjffdu commented May 4, 2018

@prabhjyotsingh @r-kamath Could you help take a look at it ? It seems a necessary feature.

@jongyoul
Copy link
Member

jongyoul commented May 4, 2018

@Savalek I agree with unexpected ignorances feel bad. Can you add test cases both of backend and frontend? And please make sure the relationship with personalized mode.

This is an idea but needs to think of how we improve this feature? If this mode is disabled, all writers can write in the same way? otherwise, prohibit it except owner? When personalized mode is enabled, how does this feature behave?

Copy link
Member

@jongyoul jongyoul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left comments. BTW, This feature itself is very charming as others think of it.

@@ -460,6 +468,20 @@ private void removeConnectionFromAllNote(NotebookSocket socket) {
}
}

private void collaborativeStatusCheck(String noteId, List<NotebookSocket> socketList) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To fit method name schema, checkCollaborateiveStatus looks more proper.

@@ -460,6 +468,20 @@ private void removeConnectionFromAllNote(NotebookSocket socket) {
}
}

private void collaborativeStatusCheck(String noteId, List<NotebookSocket> socketList) {
if (!collaborativeModeList.containsKey(noteId)) {
collaborativeModeList.put(noteId, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, it looks enough to make the type of collaborativeModeList to Set<String>. It's exactly same usage for now.

@@ -1284,6 +1306,42 @@ private void updateParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
}
}

private void patchParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage) throws IOException {
String paragraphId = (String) fromMessage.get("id");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I, absolutely, know there're many cases like these kinds of usages but we need to fix all of them. It might occur ClassCaseException


String noteId = getOpenNoteId(conn);
if (noteId == null) {
noteId = (String) fromMessage.get("noteId");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, too.

Paragraph p = note.getParagraph(paragraphId);

DiffMatchPatch dmp = new DiffMatchPatch();
String patchText = (String) fromMessage.get("patch");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, too.

(LinkedList<DiffMatchPatch.Patch>) dmp.patchFromText(patchText);

String paragraphText = p.getText();
paragraphText = (String) dmp.patchApply(patches, paragraphText)[0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This, too.


// javascript add "," if change contains several patches
patchText = patchText.replace(",@@", "@@");
LinkedList<DiffMatchPatch.Patch> patches =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dm.patchFromText() returns List<DiffMatchPathc.Path> type. Is there any specific reason to use LinkedList?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patchApply(LinkedList<DiffMatchPatch.Patch> patches, String text)
First argunment requires LinkedList<DiffMatchPatch.Patch>

}
setParagraphMode(session, dirtyText, editor.getCursorPosition());
if ($scope.cursorPosition && $scope.cursorPosition && $scope.cursorPosition) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know well about front, but what is this for?

@@ -1263,6 +1282,11 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
$route.current.pathParams.noteId);
};

const patchParagraph = function(paragraph, patch) {
const id = paragraph.id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks unnecessary as id is used only once. and this function is only used inside sendPatch. Isn't it ok to merge this function into that?

if (newPosition && newPosition.row && newPosition.column) {
$scope.cursorPosition = $scope.editor.getCursorPosition();
} else {
console.log('NOOOOOOO Save');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change the level of the log with a specific comment?

@Savalek
Copy link
Contributor Author

Savalek commented May 5, 2018

@jongyoul, I have corrected.

@r-kamath
Copy link
Member

r-kamath commented May 7, 2018

@Savalek Thanks for this new feature. IMHO it will be nice to have an option to turn on/off the collaborative mode and a way to show the number of users collaborating.
something like users with a proper tooltip.

Copy link
Member

@jongyoul jongyoul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM for the backend side.

@Savalek
Copy link
Contributor Author

Savalek commented May 7, 2018

@r-kamath, what do you mean by turn off/on option?
This option will be global and enable/disable will only be available for the server administrator?

@Savalek
Copy link
Contributor Author

Savalek commented May 7, 2018

@jongyoul, I do not like it when try {...} catch {...} is used many times in the code. Therefore, I slightly changed the handling of ClassCastException. Could you look at the new implementation and say what you think about it?

@r-kamath
Copy link
Member

r-kamath commented May 7, 2018

@Savalek

This option will be global and enable/disable will only be available for the server administrator?

Right, a property in zeppelin-site.xml to control this feature will be good to have option. Just in case if anyone wants to turn it off.
Similar to https://github.com/apache/zeppelin/blob/master/conf/zeppelin-site.xml.template#L552-L557 but in this case default true.

<button type="button"
class="btn btn-default btn-xs"
ng-show="collaborativeMode"
tooltip-placement="bottom" uib-tooltip="Users who watch this note: {{collaborativeModeUsers.join(', ')}}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@jongyoul
Copy link
Member

jongyoul commented May 7, 2018

@Savalek Thanks!! That’s what I want to do exactly. I also don’t like to use try and catch several times.

Copy link
Member

@jongyoul jongyoul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, could you please add test cases for your feature?

@Savalek
Copy link
Contributor Author

Savalek commented May 8, 2018

@jongyoul, I added tests.

Copy link
Contributor

@prabhjyotsingh prabhjyotsingh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<property>
<name>zeppelin.notebook.collaborative.mode.enable</name>
<value>true</value>
<description>Enable basic opportunity for collaborative editing. Does not change the logic of operation if the note is used by one person.</description>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR: Ideal to have a long description in the documentation. "Enable collaborative mode feature" is should be sufficient here.

@jongyoul
Copy link
Member

jongyoul commented May 9, 2018

@Savalek Thanks for adding e2e test but can you add also functional test for your backend code? It would be better to make a mock for NotebookServer to check if broadcast is called properly with each cases.

@Savalek
Copy link
Contributor Author

Savalek commented May 18, 2018

@r-kamath, @prabhjyotsingh, i fixed the description

@Savalek
Copy link
Contributor Author

Savalek commented May 18, 2018

@jongyoul, I added tests for backend.

Copy link
Member

@jongyoul jongyoul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. LGTM

@jongyoul
Copy link
Member

@Savalek BTW, https://travis-ci.org/Savalek/zeppelin/builds/380644746 Can you check your Travis? Zeppelin has a lot of tricky tests but, in your case, it's a bit different. Do you know the reason?

@Savalek
Copy link
Contributor Author

Savalek commented May 21, 2018

@jongyoul, I run two tests. "master" and "zeppelin-3307 merge master." Travis shows the same result. Errors in this tests identical.

travis

@jongyoul
Copy link
Member

Thanks. Checked. I’ve saw that all results were error except the first one. LGTM

@jongyoul
Copy link
Member

Will merge it if there's no further discussion

Copy link
Member

@felixcheung felixcheung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool!

# Conflicts:
#	zeppelin-web/src/app/notebook/notebook.css
@Savalek
Copy link
Contributor Author

Savalek commented Jun 1, 2018

Fixed conflicts. What about merge?

@asfgit asfgit closed this in c972e25 Jun 7, 2018
@Savalek Savalek deleted the ZEPPELIN-3307 branch June 7, 2018 08:00
@jongyoul
Copy link
Member

jongyoul commented Jul 6, 2018

This feature could replace "personalized mode". I will create PR to remove "personalized mode". Thank you @Savalek again.

@mebelousov
Copy link
Contributor

@jongyoul As I understand personal mode allows users run paragraphs and have different views and different results due user chosen values in dynamic forms.
I'm against the removal of personal mode.

@jongyoul
Copy link
Member

jongyoul commented Jul 6, 2018

@mebelousov The main purpose of "personalized mode" is to keep the current user's view. But if the user refreshes the browser, it changes the newest one. Do you think it's enough?

@mebelousov
Copy link
Contributor

@jongyoul I see next usecase. 10 users open the note, put client ID in dynamic form, refresh note, get and process data result. In this case getting of default note view is OK.
That is in personal mode we may not save note updates.

@jongyoul
Copy link
Member

jongyoul commented Jul 6, 2018

Git it. Thanks. I thought it might not be useful. but I might be wrong. Thank you.

mckartha pushed a commit to syntechdev/zeppelin that referenced this pull request Aug 9, 2018
### What is this PR for?
Now if the note is opened in several tabs (or several users are watching or editing it), then there may be problems. Loss of code entered by the user, reset the cursor position.
This PR adds a basic opportunity for collaborative editing. For the organization of joint editing, the library diff-match-patch is used.
PR does not change the logic of operation if the note is used by one person.
Also, maybe this will solve the problem with [ZEPPELIN-3131](https://issues.apache.org/jira/browse/ZEPPELIN-3131).

### What type of PR is it?
Improvement

### What is the Jira issue?
[ZEPPELIN-3307](https://issues.apache.org/jira/browse/ZEPPELIN-3307)

### Screenshots (if appropriate)

![gif](https://user-images.githubusercontent.com/30798933/37095049-e6c64e32-2225-11e8-96c8-517ac745a254.gif)

### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? no

Author: Savalek <def113@mail.ru>

Closes apache#2848 from Savalek/ZEPPELIN-3307 and squashes the following commits:

2347eb5 [Savalek] Merge remote-tracking branch 'apache/master' into ZEPPELIN-3307
3688d4b [Savalek] [ZEPPELIN-3307] added description in documentation. add tests.
566f844 [Savalek] [ZEPPELIN-3307] add tests for collaborative mode
88a9640 [Savalek] [ZEPPELIN-3307] checkstyle fix
32beb3d [Savalek] [ZEPPELIN-3307] add collaborative mode enable/disable option to zeppelin-site.xml
47e7db4 [Savalek] [ZEPPELIN-3307] small refactoring
8fad55a [Savalek] [ZEPPELIN-3307] small refactoring
b4c5b20 [Savalek] [ZEPPELIN-3307] add collaborative users list to tooltip. refactoring.
b131246 [Savalek] [ZEPPELIN-3307] - refactoring
d8e6cde [Savalek] [ZEPPELIN-3307] resolve merge conflicts
fbaa809 [Savalek] Merge remote-tracking branch 'apache/master' into ZEPPELIN-3307
8651f76 [Savalek] delete debug
a1700d5 [Savalek] codestyle fix
d7f449c [Savalek] Merge branch 'master' into ZEPPELIN-3307
4463ff0 [Savalek] coop icon add
f87ab50 [Savalek] coop_raw_1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
7 participants