Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Added conversation import web service #37

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from

Conversation

ruKurz
Copy link
Collaborator

@ruKurz ruKurz commented Aug 4, 2017

This commit adds an importer web service to the Smarti Backend application.
This implementation is a first draft and not yet final. Any comments are appreciated.

solves #36

Usage:

1. Importing a JSON file that has been exported from a Rocket.Chat MongoDB

curl -X GET "http://localhost:8080/import/test.assistify.de/rocketChatJSONFile" -H "accept: application/json" -H "content-type: application/json" -d "{ \"json_file_url\": \"file:///Users/rudiger/projects/smarti/application/src/test/resources/export-sapsus-faqs.json\"}"

The file import can handle Rocket.Chat MongoDB exported JSON that can be crated like this:

db.rocketchat_room.aggregate([
    { $match: { "name": { $regex : "faq" } } }, 
    { $lookup: {
        from: "rocketchat_message",
        localField: "_id",
        foreignField: "rid",
        as: "messages"
    } }
])

2. Importing direct from RocketChat MongoDB

curl -X GET "http://localhost:8080/import/test.localhost/rocketChatMongoDB" -H "accept: application/json" -H "content-type: application/json" -d "{ \"dbname\": \"rocketchat\", \"filter_field\": \"expertise\", \"filter_value\": \"assistify\", \"host\": \"localhost\", \"message_collection\": \"rocketchat_message\", \"port\": 27017, \"room_collection\": \"rocketchat_room\"}"

3. Importing from Rocket.Chat Webservice
The idea is to use the REST API of Rocket.Chat to get conversations that will be than imported into Smarti. In the current commit this importer is not finished yet.

4. Importing from Slack or other chat platforms

5. Importing from E-Mail server (Pop/Imap)

6. Importing via web crawler, e.g. FAQ-websites

@ghost ghost assigned ruKurz Aug 4, 2017
@ghost ghost added the in progress label Aug 4, 2017
@tkurz tkurz self-requested a review August 7, 2017 09:46
private static final DateFormat ISODateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

@Autowired
private ConversationWebservice conversationWebservice;
Copy link
Member

Choose a reason for hiding this comment

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

Do not use Webservices in a Service. Use the conversationService instead.

StringBuffer input = new StringBuffer();
input.append(I_ImporterService.JSON_RESULT_WRAPPER_START);

while (iterator.hasNext()) {
Copy link
Member

Choose a reason for hiding this comment

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

Could be more elegant with guava, java 8 ..., like e.g.:
ImmutableList.copyOf(iterator).stream().map(Document::toJson).collect(Collectors.joining(","));
Or you could use JacksonMapper (as the Document implements map) for the whole thing like:

ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(Collections.singletonMap("export",ImmutableList.copyOf(iterator)));

(untested code !;)

import io.redlink.smarti.services.importer.I_ConversationSource;
import io.redlink.smarti.services.importer.I_ConversationTarget;

public interface I_ImporterService {
Copy link
Member

Choose a reason for hiding this comment

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

We should find a common way of classnaming. In other projects we used to use NAME for interfaces and NAMEImpl for implementations. But I like this approach. But at least we should bring everything to a one naming scheme.

Copy link
Member

Choose a reason for hiding this comment

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

I would put the Interface in another package (e.g. api)

private RocketChatEndpoint rcEndpoint;

public ImporterService() {
rcEndpoint = new RocketChatEndpoint(null, 0, null);
Copy link
Member

Choose a reason for hiding this comment

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

Use spring to manage your service beans. The best way is:

private RocketChatEndpoint rcEndpoint;
     
public ImporterService(@Autowired RocketChatEndpoint rcEndpoint) {
   this.rcEndpoint = rcEndpoint;
}

log.info("Start export at : " + (new Date().getTime() - time) + " ms");
String export = source.exportConversations();
log.info("Start parsing at : " + (new Date().getTime() - time) + " ms");
JSONObject eportedJSON = (JSONObject) new JSONParser().parse(export);
Copy link
Member

Choose a reason for hiding this comment

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

Why source.exportConversation does not create an Object directly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The idea was to avoid duplicated code, since
JSONObject eportedJSON = (JSONObject) new JSONParser().parse(export);
would be the same in each Source implementation.

Could you please provide some arguments why you'll expect the Source itself should create the Object?

*
* @throws Exception if something goes wrong
*/
String exportConversations() throws Exception;
Copy link
Member

Choose a reason for hiding this comment

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

Could return an object (as already mentioned)

ObjectMapper mapper = new ObjectMapper();

try {
E_SourceType type = E_SourceType.valueOf(sourceType);
Copy link
Member

Choose a reason for hiding this comment

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

Could be done nicer with a factory

public JSONObject asJSON() {

try {
return (JSONObject) new JSONParser().parse(new ObjectMapper().writeValueAsString(this));
Copy link
Member

Choose a reason for hiding this comment

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

Why do you need this? As far as I can see you always use the toJsonString method on things produces by this method. So a direct serialization with ObjectMapper is more straight forward.

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
Copy link
Member

Choose a reason for hiding this comment

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

Why the order of properties matters?

}

@Test
public void testJSONFile() throws ClientProtocolException, IOException, Exception {
Copy link
Member

Choose a reason for hiding this comment

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

There is not test right ;) So no assertions oa. If you want to do a nearly full integration which is still testable you could mock the conversation repository. Or test the units and mock the services. e.g. with Mockito.

@tkurz
Copy link
Member

tkurz commented Aug 8, 2017

One thing is absolutely necessary to change: There are services which uses webservices (which is not a good style) which itself use services again. You should use services directly. Some things can be more elegant. The import interface could be rethought.

- corrections from review
- Do not use Webservices in a Service.
- Use spring to manage your service beans.
- Just use the ConversationService and Message object.
- Use JacksonMapper / direct serialization with ObjectMapper
- Removed order from JSON/Beans
@ruKurz
Copy link
Collaborator Author

ruKurz commented Aug 29, 2017

What's missing here, let me summarize:

Major implementation issues

  • for capabilities of mass imports, a 'MessageStream' should be implemented
  • the implementation over the Rocket.Chat API is incomplete

Minor implementation issues

  • the creation of the specific 'source' exporter should be done by a factory
  • the 'export' should produce some 'Object' instead a 'String'
  • the generalized import function is superfluous

Missing documentation and tests

  • API /docs for external usage
  • JavaDoc, especially for interfaces
  • Meaningful tests should be implemented

Overall comment

  • Naming of interfaces, class names and packaging as well as
  • code style has not been defined yet.

A good importer ....

The code for export as well as a scheduler should not be part of the Smarti project.
E.g. the knowledge about how to export Rocket.Chat via API is not part of Smart's core.
Maybe it's is a good idea to just implement the importer API and leave it open how to get an export.
This importer should be capable for a generic message streams following different schemas and support a mapping.

A interface could look like:

package io.redlink.smarti.services;

public interface I_Importer {

  /**
   * Imports messages from a message input stream.<p>
   *
   * Provides capability to addapt/import a conversation source to Smarti.
   * Smarti does not need to know the message's structure of the source.
   * A maaping tells how the source message fromat can be transfered.
   *
   * @param input streams the messages from an external conversaion source
   * @param schema descripes the structure of a single message of the source
   * @param mapping maps the external message format to Smarti message format
   *
   * @return the report of the import: Success / Fail / Summary / Loginfos, ...
   */
  Report import (MessageStream input, MessageSchema schema, MessageMapping mapping);
}

Implementing such an interface would allow to externalize the knowledge of exporting different source systems into third party libs. What would keep the code of Smarti clean!

@tkurz tkurz added this to the Delta-Sprint milestone Sep 7, 2017
@ruKurz
Copy link
Collaborator Author

ruKurz commented Sep 27, 2017

@tkurz did you have a chance to have a closer look into this PR?

@ja-fra ja-fra removed this from the v0.6.0 milestone Nov 27, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants