Creating A Dockerfile For Yacy Grid MCP

The YaCy Grid is the second-generation implementation of YaCy, a peer-to-peer search engine. A YaCy Grid installation consists of a set of micro-services which communicate with each other using a common infrastructure for data persistence. The task was to deploy the second-generation of YaCy Grid. To do so, we first had created a Dockerfile. This dockerfile should start the micro services such as rabbitmq, Apache ftp and elasticsearch in one docker instance along with MCP. The microservices perform following tasks:

  • Apache ftp server for asset storage.
  • RabbitMQ message queues for the message system.
  • Elasticsearch for database operations.

To launch these microservices using Dockerfile, we referred to following documentations regarding running these services locally: https://github.com/yacy/yacy_grid_mcp/blob/master/README.md

For creating a Dockerfile we proceeded as follows:

FROM ubuntu:latest
MAINTAINER Harshit Prasad# Update
RUN apt-get update
RUN apt-get upgrade -y# add packages
# install jdk package for java
RUN apt-get install -y git openjdk-8-jdk

#install gradle required for build
RUN apt-get update && apt-get install -y software-properties-common
RUN add-apt-repository ppa:cwchien/gradle
RUN apt-get update
RUN apt-get install -y wget
RUN wget https://services.gradle.org/distributions/gradle-3.4.1-bin.zip
RUN mkdir /opt/gradle
RUN apt-get install -y unzip
RUN unzip -d /opt/gradle gradle-3.4.1-bin.zip
RUN PATH=$PATH:/opt/gradle/gradle-3.4.1/bin
ENV GRADLE_HOME=/opt/gradle/gradle-3.4.1
ENV PATH=$PATH:$GRADLE_HOME/bin
RUN gradle -v

# install apache ftp server 1.1.0
RUN wget http://www-eu.apache.org/dist/mina/ftpserver/1.1.0/dist/apache-ftpserver-1.1.0.tar.gz
RUN tar xfz apache-ftpserver-1.1.0.tar.gz

# install RabbitMQ server
RUN wget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.6/rabbitmq-server-generic-unix-3.6.6.tar.xz
RUN tar xf rabbitmq-server-generic-unix-3.6.6.tar.xz

# install erlang language for RabbitMQ
RUN apt-get install -y erlang

# install elasticsearch
RUN wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.5.0.tar.gz
RUN sha1sum elasticsearch-5.5.0.tar.gz
RUN tar -xzf elasticsearch-5.5.0.tar.gz

# clone yacy_grid_mcp repository
RUN git clone https://github.com/nikhilrayaprolu/yacy_grid_mcp.git
WORKDIR /yacy_grid_mcp

RUN cat docker/configftp.properties > ../apacheftpserver1.1.0/res/conf/users.properties

# compile
RUN gradle build
RUN mkdir data/mcp-8100/conf/ -p
RUN cp docker/config-mcp.properties data/mcp-8100/conf/config.properties
RUN chmod +x ./docker/start.sh

# Expose web interface ports
# 2121: ftp, a FTP server to be used for mass data / file storage
# 5672: rabbitmq, a rabbitmq message queue server to be used for global messages, queues and stacks
# 9300: elastic, an elasticsearch server or main cluster address for global database storage
EXPOSE 2121 5672 9300 9200 15672 8100

# Define default command.
ENTRYPOINT [“/bin/bash”, “./docker/start.sh”]

 

We have created a start.sh file to start RabbitMQ and Apache FTP services. At the end, for compilation gradle run will be executed.

adduser –disabled-password –gecos ” r
adduser r sudo
echo ‘%sudo ALL=(ALL) NOPASSWD:ALL’ >> /etc/sudoers
chmod a+rwx /elasticsearch-5.5.0 -R
su -m r -c ‘/elasticsearch-5.5.0/bin/elasticsearch -Ecluster.name=yacygrid &’
cd /apacheftpserver1.1.0
./bin/ftpd.sh res/conf/ftpdtypical.xml &
/rabbitmq_server-3.6.6/sbin/rabbitmq-server -detached
sleep 5s;
/rabbitmq_server-3.6.6/sbin/rabbitmq-plugins enable rabbitmq_management
/rabbitmq_server3.6.6/sbin/rabbitmqctl add_user yacygrid password4account
echo [{rabbit, [{loopback_users, []}]}]. >> /rabbitmq_server-3.6.6/etc/rabbitmq/rabbitmq.config
/rabbitmq_server-3.6.6/sbin/rabbitmqctl set_permissions -p / yacygrid “.*” “.*” “.*”
cd /yacy_grid_mcp
sleep 5s;
gradle run

 

start.sh will first add username and then password. Then it will start RabbitMQ along with Apache FTP.  For username and password, we have created a separate files to configure their properties during Docker run which can be found here:

The logic behind running all the microservices in one docker instance was: creating each container for microservice and then link those containers with the help of docker-compose.yml file.

The Dockerfile which we have created was corresponding to one image. Another image was elasticsearch which was linked to this Dockerfile. The latest version of elasticsearch image was already available on their site: https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html

We configured the docker-compose.yml file according to the reference link provided above. The docker-compose file can be found here: https://github.com/yacy/yacy_grid_mcp/blob/master/docker/docker-compose.yml

The source code for the implementation of whole structure can be found here: https://github.com/yacy/yacy_grid_mcp/tree/master/docker

Resources

 

Implementing Advanced Search Feature In Susper

Susper has been provided ‘Advanced Search’ feature which provides the user a great experience to search for desired results. Advanced search has been implemented in such a way it shows top authors, top providers, and distribution regarding protocols. Users can choose any of these options to get best results.

We receive data of each facet name from Yacy using yacy search endpoint. More about yacy search endpoint can be found here:  http://yacy.searchlab.eu/solr/select?query=india&fl=last_modified&start=0&rows=15&facet=true&facet.mincount=1&facet.field=host_s&facet.field=url_protocol_s&facet.field=author_sxt&facet.field=collection_sxt&wt=yjson

For implementing this feature, we created Actions and Reducers using concepts of Redux. The implemented actions can be found here: https://github.com/fossasia/susper.com/blob/master/src/app/actions/search.ts

Actions have been implemented because these actually represent some kind of event. For e.g. like the beginning of an API call here.

We also have created an interface for search action which can be found here under reducers as filename index.ts: https://github.com/fossasia/susper.com/blob/master/src/app/reducers/index.ts

Reducers are a pure type of function that takes the previous state and an action and returns the next state. We have used Redux to implement actions and reducers for the advanced search.

For advanced search, the reducer file can be found here: https://github.com/fossasia/susper.com/blob/master/src/app/reducers/search.ts

The main logic has been implemented under advancedsearch.component.ts:

export class AdvancedsearchComponent implements OnInit {
  querylook = {}; // array of urls
  navigation$: Observable<any>;
  selectedelements: Array<any> = []; // selected urls by user
changeurl
(modifier, element) {
// based on query urls are fetched
// if an url is selected by user, it is decoded
  this.querylook[‘query’] = this.querylook[‘query’] + ‘+’ + decodeURIComponent(modifier);
  this.selectedelements.push(element);
// according to selected urls
// results are loaded from yacy
  this.route.navigate([‘/search’], {queryParams: this.querylook});
}

// same method is implemented for removing an url
removeurl(modifier) {
  this.querylook[‘query’] = this.querylook[‘query’].replace(‘+’ + decodeURIComponent(modifier), );

  this.route.navigate([‘/search’], {queryParams: this.querylook});
}

 

The changeurl() function replaces the query with a query and selected URL and searches for the results only from the URL provider. The removeurl() function removes URL from the query and works as a normal search, searching for the results from all providers.

The source code for the implementation of advanced search feature can be found here: https://github.com/fossasia/susper.com/tree/master/src/app/advancedsearch

Resources

Implementing Sort By Date Feature In Susper

 

Susper has been given ‘Sort By Date’ feature which provides the user with latest results with the latest date. This feature enhances the search experience and helps users to find desired results more accurately. The sorting of results date wise is done by yacy backend which uses Apache Solr technology.

The idea was to create a ‘Sort By Date’ feature similar to the market leader. For example, if a user searches for keyword ‘Jaipur’ then results appear to be like this:

If a user wishes to get latest results, they can use ‘Sort By Date’ feature provided under ‘Tools’.

The above screenshot shows the sorted results.

You may however notice that results are not arranged year wise. Currently, the backend work for this is being going on Yacy and soon will be implemented on the frontend as well once backend provide us this feature.

Under ‘Tools’ we created an option for ‘Sort By Date’ simply using <li> tag.

<ul class=dropdownmenu>
  <li (click)=filterByDate()>Sort By Date</li>
</ul>

When clicked, it calls filterByDate() function to perform the following task:

filterByDate() {
  let urldata = Object.assign({}, this.searchdata);
  urldata.query = urldata.query.replace(/date, “”);
  this.store.dispatch(new queryactions.QueryServerAction(urldata));
}
Earlier we were using ‘last_modified desc’ attribute provided by Solr for sorting out dates in descending order. In June 2017, this feature was deprecated with a new update of Solr. We are using /date attribute in query for sorting out results which is being provided by Solr.

 

Implementation of Text-To-Speech Feature In Susper

Susper has been given a voice search feature through which it provides the user a better experience of search. We introduced to enhance the speech recognition by adding Speech Synthesis or Text-To-Speech feature. The speech synthesis feature should only work when a voice search is attempted.

The idea was to create speech synthesis similar to market leader. Here is the link to YouTube video showing the demo of the feature: Video link

In the video, it will show demo :

  • If a manual search is used then the feature should not work.
  • If voice search is used then the feature should work.

For implementing this feature, we used Speech Synthesis API which is provided with Google Chrome browser 33 and above versions.

window.speechSynthesis.speak(‘Hello world!’); can be used to check whether the browser supports this feature or not.

First, we created an interface:

interface IWindow extends Window {
  SpeechSynthesisUtterance: any;
  speechSynthesis: any;
};

 

Then under @Injectable we created a class for the SpeechSynthesisService.

export class SpeechSynthesisService {
  utterence: any;  constructor(private zone: NgZone) { }  speak(text: string): void {
  const { SpeechSynthesisUtterance }: IWindow = <IWindow>window;
  const { speechSynthesis }: IWindow = <IWindow>window;  this.utterence = new SpeechSynthesisUtterance();
  this.utterence.text = text; // utters text
  this.utterence.lang = ‘en-US’; // default language
  this.utterence.volume = 1; // it can be set between 0 and 1
  this.utterence.rate = 1; // it can be set between 0 and 1
  this.utterence.pitch = 1; // it can be set between 0 and 1  (window as any).speechSynthesis.speak(this.utterence);
}// to pause the queue of utterence
pause(): void {
  const { speechSynthesis }: IWindow = <IWindow>window;
const { SpeechSynthesisUtterance }: IWindow = <IWindow>window;
  this.utterence = new SpeechSynthesisUtterance();
  (window as any).speechSynthesis.pause();
 }
}

 

The above code will implement the feature Text-To-Speech.

The source code for the implementation can be found here: https://github.com/fossasia/susper.com/blob/master/src/app/speech-synthesis.service.ts

We call speech synthesis only when voice search mode is activated. Here we used redux to check whether the mode is ‘speech’ or not. When the mode is ‘speech’ then it should utter the description inside the infobox.

We did the following changes in infobox.component.ts:

import { SpeechSynthesisService } from ../speechsynthesis.service;

speechMode: any;

constructor(private synthesis: SpeechSynthesisService) { }

this.query$ = store.select(fromRoot.getwholequery);
this.query$.subscribe(query => {
  this.keyword = query.query;
  this.speechMode = query.mode;
});

 

And we added a conditional statement to check whether mode is ‘speech’ or not.

// conditional statement
// to check if mode is ‘speech’ or not
if (this.speechMode === speech) {
  this.startSpeaking(this.results[0].description);
}
startSpeaking(description) {
  this.synthesis.speak(description);
  this.synthesis.pause();
}

 

 

 

Adding Transition Effect Using RxJS And CSS In Voice Search UI Of Susper

 

Susper has been given a voice search feature through which it provides a user with a better experience of search. We introduced to enhance the speech-recognition user interface by adding transition effects. The transition effect was required to display appropriate messages according to voice being detected or not. The following messages were:

  • When a user should start a voice search, it should display ‘Speak Now’ message for 1-2 seconds and then show up with message ‘Listening…’ to acknowledge user that now it is ready to recognize the voice which will be spoken.
  • If a user should do not speak anything, it should display ‘Please check audio levels or your microphone working’ message in 3-4 seconds and should exit the voice search interface.

The idea of speech UI was taken from the market leader and it was implemented in a similar way. On the homepage, it looks like this:

On the results page, it looks like this:

For creating transitions like, ‘Listening…’ and ‘Please check audio levels and microphone’ messages, we used CSS, RxJS Observables and timer() function.

Let’s start with RxJS Observables and timer() function.

RxJS Observables and timer()

timer() is used to emit numbers in sequence in every specified duration or after a given duration. It acts as an observable. For example:

let countdown = Observable.timer(2000);
The above code will emit value of countdown in 2000 milliseconds. Similarly, let’s see another example:
let countdown = Observable.timer(2000, 6000);
The above code will emit value of countdown in 2000 milliseconds and subsequent values in every 6000 milliseconds.
export class SpeechToTextComponent implements OnInit {
  message: any = ‘Speak Now’;
  timer: any;
  subscription: any;
  ticks: any;
  miccolor: any = #f44;
}
ngOnInit() {
  this.timer = Observable.timer(1500, 2000);
  this.subscription = this.timer.subscribe(t => {
  this.ticks = t;// it will throw listening message after 1.5   sec
  if (t === 1) {
    this.message = Listening;
  }// subsequent events will be performed in 2 secs interval
  // as it has been defined in timer()
  if (t === 4) {
    this.message = Please check your microphone audio levels.;
    this.miccolor = #C2C2C2;
}// if no voice is given, it will throw audio level message
// and unsubscribe to the event to exit back on homepage
  if (t === 6) {
    this.subscription.unsubscribe();
    this.store.dispatch(new speechactions.SearchAction(false));
  }
 });
}
The above code will throw following messages at a particular time. For creating the text-animation effect, most developers go for plain javascript. The text-animation effects can also be achieved by using pure CSS.

Text animation using CSS

@webkitkeyframes typing {from {width:0;}}
.spch {
  fontweight: normal;
  lineheight: 1.2;
  pointerevents: none;
  position: none;
  textalign: left;
  –webkitfontsmoothing: antialiased;
  transition: opacity .1s easein, marginleft .5s easein,                  top  0s linear 0.218s;
  –webkitanimation: typing 2s steps(21,end), blinkcaret .5s                       stepend infinite alternate;
  whitespace: nowrap;
  overflow: hidden;
  animationdelay: 3.5s;
}
@keyframes specifies animation code. Here width: 0; tells that animation begins from 0% width and ends to 100% width of the message. Also, animation-delay: 3.5s has been adjusted w.r.t timer to display messages with animation at the same time.
This is how it works now:

The source code for the implementation can be found in this pull request: https://github.com/fossasia/susper.com/pull/663

Resources:

 

 

Hiding Intelligence of Susper When a Query is Empty or Erased with Angular

Recently, we have implemented intelligence feature in Susper using SUSI chat API to provide users answer a question without going deeper in search results. When a user types “height of Trump”, it shows answer like this:

Problem which we faced after implementing the feature:

When a user was erasing a query or query field was empty, Susper was still showing the answer of the intelligence component like this:

The answer should not be displayed when a query is empty because the user is not asking any question. The answer was still displayed because it had received a response from SUSI API.

How did we solve the problem?

The problem was solved in two ways.

  1. By using if/else condition: We checked if the statement shown inside the component is similar to the if-and-else condition. If the condition is true, it should hide the component.
  2. Using [hidden] attribute method: The Angular 4 supports [hidden] attribute which acts as { display:none; } . [hidden] attribute generally works as ngShow and ngHide which was earlier supported by Angular 2.

We preferred both the methods to solve the problem. The intelligence component is being loaded inside results component using <app-intelligence> element. Further, we added [hidden] attribute to this element like this :

<appintelligence [hidden]=“hideIntelligence”></app-intelligence>
We created hideIntelligence as variable and assign it as boolean. To check if a query is empty, searchdata variable was used.
searchdata: any = {
  query: ‘ ‘,
  rows: 10,
  start: 0
};
And then checked if a query is empty using if-else condition :
// checks if query is empty or erased
if (this.searchdata.query === ‘ ‘) {// display: none; is true
  this.hideIntelligence = true;

} else {
// display: none; is false
  this.hideIntelligence = false;
}

 

Applying this solution, we succeeded in hiding the intelligence component. We would also had used *ngIf statement but we preferred using [hidden]. [hidden] modifies the display property.  *ngIf is a structural directive which creates or destroys content inside DOM.

The source code for the implementation can be found here: https://github.com/fossasia/susper.com/pull/613

Resources:

 

Changing Dimensions of Search Box Dynamically in Susper

Earlier the Susper search box had a fixed dimension. When a user types in a query, the dimensions of the search box remained fixed. This approach resulted in several issues like:

  • Matching the dimensions of the search bar following the market leader.
  • When dimensions are dynamically changing, it should not disturb alignment w.r.t tabs in the results page.

What actually happens is, when a user enters a query, the search box quickly changes its dimensions when results appear. I will be discussing below how we achieved this goal.

On the home page, we created the dimensions of a search bar with 584 x 44 pixels.

On the results page, we created the dimensions of search bar 632 x 44 similar to market leader:

How we proceeded?

Susper is built on Angular v4.1.3. It automatically comes with a function ngOnInit() whenever a new component has been created. ngOnInit() is a part of life cycle hook in Angular 4 (in Angular 2 as well). The function is called up or initialized when the component is rendered completely. This was the key for changing dimensions of search bar dynamically as soon as a component is created.

What happens is when a user types a query on the homepage and hits enter then, results component is created. As soon as, it is created – ngOnInit() function is called.

The default dimensions of search bar have been provided as follows:

search-bar.component.css

#navgroup {
  height: 44px;
  width: 584px;
}
When the homepage loads up, dimensions by default are 584 x 44.

search-bar.component.ts

private navbarWidth: any;
ngOnInit() {
  this.navbarWidth = 632px;
}

search-bar.component.html

We used [style.width] attribute to change the dimensions dynamically. Add this attribute inside input element.

<input #input type=“text” name=“query” class=“form-control” id=“nav-input” (ngModelChange)=“onquery($event)” [(ngModel)]=“searchdata.query” autocomplete=“off” (keypress)=“onEnter($event)” [style.width]=“navbarWidth”>
As soon as results component is loaded, the dimensions of search bar change to 632 x 44. In this way, we change the dimensions of search bar dynamically as soon as results are loaded.

Resources

Aligning Images to Same Height Maintaining Ratio in Susper

In this blog, I’ll be sharing the idea how we have perfectly aligned images to the same height in Susper without disturbing their ratio. When it comes to aligning images perfectly, they should have:

  • Same height.
  • A proper ratio to maintain the image quality. Many developers apply same width and height without keeping in mind about image ratio which results in:
    • Blurred image,
    • Image with a lot of pixels,
    • Cropping of an image.

Earlier Susper was having image layout like this:

In the screenshot, images are not properly aligned.  They are also not having the same height. We wanted to improve the layout of images just like market leaders Google and DuckDuckGo.

  • How we implemented a better layout for images?

<div class=“container”>
  <div class=“grid” *ngIf=“Display(‘images’)”>
    <div class=“cell” *ngFor=“let item of item$ | async”>
      <a class=“image-pointer” href=“{{item.link}}”>
        <img class=“responsive-image” src=“{{item.link}}”></a>
    </div>
  </div>
</div>
I have created a container, in which images will be loaded from yacy server. Then I have created a grid with an equal number of rows and column. I have adjusted the height and width of rows and columns to obtain a grid which contains each division as a cell. The image will load inside the cell. Each cell consists of one image.
.grid {
  paddingleft: 80px;
}
.container {
  width: 100%;
  margin: 0;
  top: 0;
  bottom: 0;
  padding: 0;
}

After implementing it, we were facing issues like cropping of the image inside a cell. So, to avoid cropping and maintain the image ratio we introduced .responsive-image class which will avoid cropping of images inside cell.

.responsiveimage {
  maxwidth: 100%;
  height: 200px;
  paddingtop: 20px;
  padding: 0.6%;
  display: inlineblock;
  float: left;
}

This is how Susper’s image section looks now:

It took some time to align images, but somehow we succeeded in creating a perfect layout for the images.

We are facing some issues regarding images. Some of them don’t appear due to broken link. This issue will be resolved soon on the server.

Resources

Refactor of Dropdown Menu in Susper

The first version of the Susper top menu was providing links to resources and tutorials. In the next version of the menu, we were looking for a menu with more colorful icons, a cleaner UI design and a menu that should appear on the homepage as well. In this blog, I will discuss about refactoring the dropdown menu. This is how earlier dropdown of Susper looks like:

We decided to create a separate component for the menu DropdownComponent.

At first, I created a drop down menu with matching dimensions similar to what Google follows. Then, I gave padding: 28px to create similar UI to market leader. This will make a dropdown menu with clean UI design. I replaced the old icons with colorful icons. In the dropdown we have:

  • Added more projects of FOSSASIA like eventyay, loklak, susi and main website of FOSSASIA. Here how it looks now :

The main problem I faced was aligning the content inside the dropdown and they should not get disturbed when the screen size changes.
I kept the each icon dimensions as 48 x 48 inside drop down menu. I also arranged them in a row. It was easy to use div element to create rows rather than using ul and li tags which were implemented earlier.

To create a horizontal grey line effect, I used the hr element. I made sure, padding remained the same above and below the horizontal line.

At the end of drop down menu, @mariobehling suggested instead of writing ‘more’, it should redirect to projects page of FOSSASIA.

This is how I worked on refactoring drop down menu and added it on the homepage as well.

Resources

Using MockBackend to test Susper Angular Front-end Code

In this blog, I’ll be sharing how we are testing services which we are using in Susper development.We’re using Angular 4 in our project as tech stack and we use Jasmine for testing purpose.

Tests are written to avoid issues which occur again and again. For example: Since we have implemented knowledge graph, we faced a lot of issues like:

  • When a user enters a query, search results appear but knowledge graph does not appear.
  • When a fresh query is entered or page is refreshed, knowledge graph does not appear.
  • The API which we have used is not responding.

We overcome this issue by writing test. The data is being taken with the help of an API. So, it will require testing using HTTP. Instead of testing like this, there is a better way by using MockBackend.

Testing with MockBackend is a more sensible approach. This allows us to mock our responses and avoid hitting the actual backend which results in boosting our testing.

To use the MockBackend feature, it requires creating a mock. For knowledge-service it looks like this:

export const MockKnowledgeApi {
  results: {
    uri: ‘http://dbpedia.org/resource/Berlin’,
    label: ‘Berlin’,
  }
  MaxHits: 5
};

To use the MockBackend feature, import MockBackend, MockConnection, BaseRequestOptions and MockKnowledgeApi.

import { MockBackend, MockConnection } from ‘@angular/http/testing’;
import { MockKnowledgeApi } from ‘./shared/mock-backend/knowledge.mock’;
import { BaseRequestOptions } from ‘@angular/http’;

Create a mock setup. In this case, we will create mock setup w.r.t HTTP because data from API is being returned as HTTP. If data, is being returned in JSON format, create a mock setup w.r.t jsonp.

const mockHttp_provider = {
  provide: Http,
  deps: [MockBackend, BaseRequestOptions],
  useFactory: (backend: MockBackend,options: BaseRequestOptions) => {
    return new Http(backend, options);
}
};

Now, describe the test suite. Inside, describe the function, don’t import MockConnection. It will throw error since it is only used to create a fake backend. It should look like this:

providers: [
  KnowledgeapiService,
  MockBackend,
  BaseRequestOptions,
  mockHttp_provider,
]
Define service as KnowledgeService and backend as MockBackend. Inject both the services in beforeEach() function.
Now to actually test the service, create a query.

const searchquery = ‘Berlin’;

The written specs should look like this. I won’t go much in detail here, but I’ll cover up the key points of code.

it(‘should call knowledge service API and return the result’, () => {
backend.connections.subscribe((connection: MockConnection) => {
const options = new ResponseOptions({
body: JSON.stringify(MockKnowledgeApi)
});connection.mockRespond(new Response(options));
expect(connection.request.method).toEqual(RequestMethod.Get);
});
Here, mockRespond will mock our response and it will test whether the service is working or not. Already, we have defined a query.

It should have a link to API and should be equal to searchquery which we have defined already as ‘Berlin’.

expect(connection.request.url).toBe(
`http://lookup.dbpedia.org/api/search/KeywordSearch` +
`?&QueryString=${searchquery}`
);

At last, it will check if it’s working or not. If it’s working, then test case will pass. Please note, it will not hit the actual backend.

service.getsearchresults(searchquery).subscribe((res) => {
expect(res).toEqual(MockKnowledgeApi);
);
In this way, we have written tests for knowledge graph to avoid future issues. We will be adding tests like these for other services as well which are being used in Susper project.

Resources