Adding Color Options in loklak Media Wall

Color options in loklak media wall gives user the ability to set colors for different elements of the media wall. Taking advantage of Angular two-way data binding property and ngrx/store, we can link up the CSS properties of the elements with concerned state properties which stores the user-selected color. This makes color customization fast and reactive for media walls.

In this blog here, I am explaining the unidirectional workflow using ngrx for updating various colors and working of color customization.

Flow Chart

The flowchart below explains how the color as a string is taken as an input from the user and how actions, reducers and component observables link up to change the current CSS property of the font color.

Working

Designing Models: It is important at first to design model which must contain every CSS color property that can be customized. A single interface for a particular HTML element of media wall can be added so that color customization for a particular element can take at once with faster rendering. Here we have three interfaces:

  • WallHeader
  • WallBackground
  • WallCard

These three interfaces are the models for the three core components of the media wall that can be customized.

export interface WallHeader {
backgroundColor: string;
fontColor: string;
}
export interface WallBackground {
backgroundColor: string;
}
export interface WallCard {
fontColor: string;
backgroundColor: string;
accentColor: string;
}

 

Creating Actions: Next step is to design actions for customization. Here we need to pass the respective interface model as a payload with updated color properties. These actions when dispatched causes reducer to change the respective state property, and hence, the linked CSS color property.

export class WallHeaderPropertiesChangeAction implements Action {
type = ActionTypes.WALL_HEADER_PROPERTIES_CHANGE;constructor(public payload: WallHeader) { }
}
export class WallBackgroundPropertiesChangeAction implements Action {
type = ActionTypes.WALL_BACKGROUND_PROPERTIES_CHANGE;constructor(public payload: WallBackground) { }
}
export class WallCardPropertiesChangeAction implements Action {
type = ActionTypes.WALL_CARD_PROPERTIES_CHANGE;constructor(public payload: WallCard) { }
}

 

Creating reducers: Now, we can proceed to create reducer functions so as to change the current state property. Moreover, we need to define an initial state which is the default state for uncustomized media wall. Actions can now be linked to update state property using this reducer when dispatched. These state properties serve two purposes:

  • Updating Query params for Direct URL.
  • Updating Media wall Colors

case mediaWallCustomAction.ActionTypes.WALL_HEADER_PROPERTIES_CHANGE: {
const wallHeader = action.payload;return Object.assign({}, state, {
wallHeader
});
}case mediaWallCustomAction.ActionTypes.WALL_BACKGROUND_PROPERTIES_CHANGE: {
const wallBackground = action.payload;return Object.assign({}, state, {
wallBackground
});
}case mediaWallCustomAction.ActionTypes.WALL_CARD_PROPERTIES_CHANGE: {
const wallCard = action.payload;return Object.assign({}, state, {
wallCard
});
}

 

Extracting Data to the component from the store: In ngrx, the central container for states is the store. Store is itself an observable and returns observable related to state properties. We have already defined various states for media wall color options and now we can use selectors to return state observables from the store. These observables can now easily be linked to the CSS of the elements which changes according to customization.

private getDataFromStore(): void {
this.wallCustomHeader$ = this.store.select(fromRoot.getMediaWallCustomHeader);
this.wallCustomCard$ = this.store.select(fromRoot.getMediaWallCustomCard);
this.wallCustomBackground$ = this.store.select(fromRoot.getMediaWallCustomBackground);
}

 

Linking state observables to the CSS properties: At first, it is important to remove all the CSS color properties from the elements that need to be customized. Now, we will instead use style directive provided by Angular in the template which can be used to update CSS properties directly from the component variables. Since the customized color received from the central store are observables, we need to use the async pipe to extract string color data from it.

Here, we are updating background color of the wall.

<span class=“wrapper”
[style.background-color]=“(wallCustomBackground$ | async).backgroundColor”>
</span>

 

For other child components, we need to use @Input Decorator to send color data as an input to it and use the style directive as used above.

Here, we are interacting with the child component i.e. media wall card component using @Input Decorator.

Template:

<media-wall-card
[feedItem]=“item”
[wallCustomCard$]=“wallCustomCard$”></media-wall-card>

 

Component:

export class MediaWallCardComponent implements OnInit {
..
@Input() feedItem: ApiResponseResult;
@Input() wallCustomCard$: Observable<WallCard>;
..
}

 

This creates a perfect binding of CSS properties in the template with the state properties of color actions. Now, we can dispatch different actions to update the state and hence, the colors of media wall.

Reference

Continue ReadingAdding Color Options in loklak Media Wall

Updating Page Titles Dynamically in Loklak Search

Page titles are native in the web platform, and are prime ways to identify any page. The page titles have been in the web platform since ages. They tell the browsers, the web scrapers and search engines about the page content in 1-2 words. Since the titles are used for wide variety of things from presentation of the page, history references and most importantly by the search engines to categorise the pages, it becomes very important for any web application to update the title of the page appropriately. In earlier implementation of loklak search the page title was a constant and was not updated regularly and this was not a good from presentation and SEO perspective.

Problem of page titles with SPA

Since loklak search is a single page application, there are few differences in the page title implementation in comparison to a server served multi page application. In a server served multi page application, the whole application is divided into pages and the server knows what page it is serving thus can easily set the title of the page while rendering the template. A simple example will be a base django template which holds the place to have a title block for the application.

<!-- base.html -->

<title>{% block title %} Lokalk Search {% endblock %}</title>

<!-- Other application blocks -->

Now for any other page extending this base.html it is very simple to update the title block by simply replacing it with it’s own title.

<!-- home.html -->

{% extendsbase.html%}

{% block title %} Home Page - Loklak Search {% endblock %}

<!-- Other page blocks -->

When the above template is rendered by the templating engine it replaces the title block of the base.html with the updated title block specific to the page, thus for each page at the rendering time server is able to update the page title, appropriately.

But in a SPA, the server just acts as REST endpoints, and all the templating is done at the client side. Thus in an SPA the page title never changes automatically, from the server, as only the client is in control of what page (route) it is showing. Thus it becomes the duty of the client side to update the title of the page, appropriately, and thus this issue of static non informative page titles is often overlooked.

Updating page titles in Loklak Search

Before being able to start solving the issue of updating the page titles it is certainly very important to understand what all are the points of change in the application where we need to update the page title.

  • Whenever the route in the application changes.
  • Whenever new query is fetched from the server.

These two are the most important places where we definitely want to update the titles. The way we achieved is using the Angular Title Service. The title service is a platform-browser service by angular which abstracts the workflow to achieve the title updation. There are are two main methods of this service get and set title, which can be used to achieve our desired behaviour. What title service do is abstract the extraction of Title Node and get and set the title values.

For updation of title for each page which is loaded we just attach an onInit lifecycle hook to the parent component of that page and, onInit we use the title service to update the title accordingly.

@Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent implements OnInit, OnDestroy {
constructor(
private titleService: Title
) { }

ngOnInit() {
this.titleService.setTitle(Loklak Search');

// Other initialization attributes and methods
}
}

Similarly other pages according to their context update the page titles accordingly using the simple title service. This solves the basic case of updation of the titles of the page when the actual route path changes, and thus component’s onInit lifecycle hook is the best place to change the title of the page.

@Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent implements OnInit, OnDestroy {
constructor(
private titleService: Title
) { }

ngOnInit() {
this.titleService.setTitle(Loklak Search');

// Other initialization attributes and methods
}
}

But when the actual route path doesn’t change and we want to update the title according to the query searched then it is not possible to do it using lifecycle hooks of the component. But fortunately, we are using the ngrx effects in our application and thus this task also again becomes much simpler to achieve in the application. In this situation again what we do is hook up a title change effect to SearchCompleteSuccessAction, and there we change the title accordingly.

@Effect({ dispatch: false })
resetTitleAfterSearchSuccess$: Observable<void>
= this.actions$
.ofType(apiAction.ActionTypes.SEARCH_COMPLETE_SUCCESS,
apiAction.ActionTypes.SEARCH_COMPLETE_FAIL)
.withLatestFrom(this.store$)
.map(([action, state]) => {
const displayString = state.query.displayString;
let title = `${displayString} - Loklak Search`;
if (action.type === apiAction.ActionTypes.SEARCH_COMPLETE_FAIL) {
title += ' - No Results';
}
this.titleService.setTitle(title);
});

Now if we look closely this effect is somewhat different from all the other effects. Firstly, the effect observable is of type void instead of type Action which is the case with other effects, and also there is is a { dispatch: false } argument passed to the constructor. Both these things are important of our resetTitle effect. As our reset title effect has no action to dispatch on it it’s execution the the observable is of type void instead of type Action, and we never want to dispatch an effect whose type is not an Action thus we set dispatch to false. Rest of the code for the effect is fairly simple, we filter all the actions and take SearchSuccess and SearchFail actions, then we get the latest value of the query display string from the store, and we use our title service to reset the title accordingly.

Conclusion

The titles are the important part of the web platform and are used by browsers and search engines to present and rank the relevance of the page. While developing a SPA it is even more important to maintain an updated title tag, as it is the only thing which actually changes about the page according to the context of the page. In loklak search, the title service is now used to update the titles of the page according to the search results.

Resources and Links

Continue ReadingUpdating Page Titles Dynamically in Loklak Search

Accessing Child Component’s API in Loklak Search

Loklak search being an angular application, comprises of components. Components provide us a way to organize the application in a more consistent way, along with providing the ability to reuse code in the application. Each component has two type of API’s public and private. Public API is the API which it exposes to the outer world for manipulating the working of the component, while private API is something which is local to the component and cannot be directly accessed by the outside world. Now when this distinction between the two is clear, it is important to state the need of these API’s, and why are they required in loklak search.

The components can never live in isolation, i.e. they have to communicate with their parent to be able to function properly. Same is the case with components of loklak search. They have to interact with others to make the application work. So how this, interaction looks like,

The rule of thumb here is, data flows down, events flow up. This is the core idea of all the SPA frameworks of modern times, unidirectional data flow, and these interactions can be seen everywhere in loklak search.

<feed-header
   [query]="query"
   (searchEvent)="doSearch($event)"></feed-header>

This is how a simple component’s API looks in loklak search. Here our component is FeedHeader and it exposes some of it’s API as inputs and outputs.

export class FeedHeaderComponent {

 @Input() query: string;

 @Output() searchEvent: EventEmitter<string> = new EventEmitter<string>();

  // Other methods and properties of the component
}

The FeedHeaderComponent ‘s class defines some inputs which it takes. These inputs are the data given to the component. Here the input is a simple query property, and the parent at the time of instantiating the component, passes the value to it’s child as [query]=”query”. This enables the one direction of API, from parent to child. Now, we also need a way for parent to be able to events generated by the child on interaction with user. For example, here we need to have a way to tell the parent to perform a search whenever user presses search button. For this the Output property searchEvent is used. The search event can be emitted by the child component independently. While the parent, if it wants to listen to child components simply do so by binding to the event and running a corresponding function whenever event is emitted (searchEvent)=”doSearch($event)”. Here the event which parent listens to is searchEvent and whenever such an event is emitted by the child a function doSearch is run by the parent. Thus this completes the event flow, from child to parent.

Now it is worth noticing that all these inputs for data and outputs for events is provided by the child component itself. They are the API of the child and parent’s job is just to bind to these inputs and outputs to bind to data and listen to events. This allows the component interactions in both directions.

@ViewChild and triggering child’s methods

The inputs are important to carry data from the parent to the child, declaratively but sometimes it is necessary for the parent to access the public API of it’s child more directly, specially the API methods to trigger an action. These methods require the way for the parent to access its child component. This is done by @ViewChild decorator. The child element which the parent wants access to, have to declare the component as, one of it’s attributes. Like in our example, the FeedHeaderComponent needs access to its child component SuggestBoxComponent, to show/hide suggest box as and when required. So here the feed header component gets the access to its child using viewchild decorator.

export class FeedHeaderComponent {

  @ViewChild(‘#suggestBox) suggestBox: SuggestBoxComponent;

  // Other properties and methods

  toggleSuggestBox() {

     this.suggestBox.toggle();
  }
}

The SuggestBoxComponent here has a public method toggle() which toggles the visibility state of the suggest box. This method is available as a component’s public API method. The parent of this component calls this method using the @ViewChild reference which it grabbed at the time of view instantiation.

export class SuggestBoxComponent {

  private suggestBoxVisible = true;

  public toggle() {

     this.suggestBoxVisible = !this.suggestBoxVisible;
  }
}

Resources and Links

  • Angular document pages
  • Basic Usage in Angular tour of heroes tutorial
  • In depth usage blog for Inputs and Outputs SitePoint Tutorial
  • Loklak Search Repo
Continue ReadingAccessing Child Component’s API in Loklak Search

Implementing Intelligence Feature in Susper

Susper gives answers to your questions using SUSI AI. We want to give users best experience while they are searching for solutions to their questions. To achieve this, we have incorporated with features like infobox and intelligence using SUSI.

Google has this feature where users can ask questions like ‘Who is president of USA?’ and get answers directly without encouraging the users to deep-dive into the search results to know the answer.

Similarly Susper gives answer to the user:

It also gives answer to question which is related to real time data like temperature.

 

How we have implemented this feature?

We used the API Endpoint of SUSI at http://api.asksusi.com/

Using SUSI API is as simple as sending query as a URL parameter in GET request http://api.susi.ai/susi/chat.json?q=YOUR_QUERY

You can also get various action types in the response. Eg: An anwser type response for http://api.susi.ai/susi/chat.json?q=hey%20susi is:

actions: [
  {
    type: "answer",
    expression: "Hi, I'm Susi"
  }
],

 

Documentation regarding SUSI is available at here.

Implementation in Susper:

We have created an Intelligence component to display answer related to a question. You can check it here: https://github.com/fossasia/susper.com/tree/master/src/app/intelligence

It takes care about rendering the information and styling of the rendered data received from SUSI API.

The intelligence.component.ts makes a call to Intelligence Service with the required query and the intelligence service makes a GETrequest to the SUSI API and retrieves the results.

Intelligence.component.ts

this.intelligence.getintelligentresponse(data.query).subscribe(res => {
  if (res && res.answers && res.answers[0].actions) {
     this.actions = res.answers[0].actions;
       for (let action of this.actions) {
         if (action.type === 'answer' && action.mood !== 'sabta') {
           this.answer = action.expression;
         } else {
             this.answer = '';
         }
      }
   } else {
       this.answer = '';
   }
});

 

Intelligence.service.ts

export class IntelligenceService {
 server = 'http://api.susi.ai';
 searchURL = 'http://' + this.server + '/susi/chat.json';
 constructor(private http: Http, private jsonp: Jsonp, private store: Store<fromRoot.State>) {
 }
 getintelligentresponse(searchquery) {
   let params = new URLSearchParams();
   params.set('q', searchquery);
   params.set('callback', 'JSONP_CALLBACK');
   return this.jsonp
     .get('http://api.asksusi.com/susi/chat.json', {search: params}).map(res =>
       res.json()

     );
 }

Whenever the getintelligenceresponse of intelligenceService is called, it creates a URLSearchParams() object and set required parameters in it and send them in jsonp.get request. We also set callback to ‘JSONP_CALLBACK’ to inform the API to send us data in JSONP.

Thereby, the intelligence component retrieves the answer and displays it with search resultson Susper.

Source code for this implementation could be found in this pull:

https://github.com/fossasia/susper.com/pull/569

Resources:

Continue ReadingImplementing Intelligence Feature in Susper

Customizing Results Count in Susper Angular Front-end

Problem: Earlier users were not having any option to customise results count in Susper.

Susper is a Frontend for Peer to Peer Search Engine Yacy built using Angular. So, we implemented ‘results count’ feature and used to have a strict restriction of only 10 results per page. Now, users can customise search results in Susper when instant results are turned off. By default, Susper shows only 10 results per page. If the user requires more results per page he can modify the count of results in Susper. To customise the result count visit http://susper.com/preferences and you will find a range bar to customise the results. Change the value of the range bar to the desired value and save it. (Right now we support only results till maximum size of 100)

How did we implement this feature?

searchsettings.component.html:

<div>
 <h4><strong>Results per page</strong></h4>
 <div class="range-slider">
   <input class="range-slider__range" type="range" [disabled]="instantresults" [(ngModel)]="resultCount" value="100" min="0" max="100">
   <span class="range-slider__value">{{resultCount}}</span>
 </div>

</div>

The user is displayed with a range slider, that could slide between 0 and 100. The value of the range slider is stored in a resultscount variable in search settings component using ngModel.

searchsettings.component.ts: Later when user clicks on save button, it triggers onSave() function.The resultscount is stored into localStorage of the browser and an action is triggered to inform all other components about the change in the value of resultscount.

 

onSave() {
 if (this.instantresults) {
   localStorage.setItem('instantsearch', JSON.stringify({value: true}));
   localStorage.setItem('resultscount', JSON.stringify({ value: 10 }));
   this.store.dispatch(new queryactions.QueryServerAction({'query': '', start: 0, rows: 10, search: false}));

 } else {
   localStorage.removeItem('instantsearch');
   localStorage.setItem('resultscount', JSON.stringify({ value: this.resultCount }));
   this.store.dispatch(new queryactions.QueryServerAction({'query': '', start: 0, rows: this.resultCount, search: false}));
 }
 this.router.navigate(['/']);
}

app.component.ts

Later new resultscount value is used in other components to request the server for search results with new resultscount.

if (localStorage.getItem('resultscount')) {
 this.store.dispatch(new queryactions.QueryServerAction({'query': '', start: 0, rows: this.resultscount, search: false}));
}

The complete working of Susper’s result count could be seen in this gif

 

Source code can be found here: https://github.com/fossasia/susper.com/pull/546 .

References

Continue ReadingCustomizing Results Count in Susper Angular Front-end

Adding Unit Tests for Services in loklak search

In Loklak search, it can be tricky to write tests for services as these services are customizable and not fixed. Therefore, we need to test every query parameter of the URL. Moreover, we need to test if service is parsing data in a correct manner and returns only data of type ApiResponse.

In this blog here, we are going to see how to build different components for unit testing services. We will be going to test Search service in loklak search which makes Jsonp request to get the response from the loklak search.json API which are displayed as feeds on loklak search. We need to test if the service handles the response in a correct way and if the request parameters are exactly according to customization.

Service to test

Search service in loklak search is one of the most important component in the loklak search. SearchService is a class with a method fetchQuery() which takes parameter and sets up URL parameters for the search.json API of loklak. Now, it makes a JSONP request and maps the API response. The Method fetchQuery() can be called from other components with parameters query and lastRecord to get the response from the server based on a certain search query and the last record to implement pagination feature in loklak search. Now as the data is retrieved, a callback function is called to access the response returned by the API. Now, the response received from the server is parsed to JSON format data to extract data from the response easily.

@Injectable()
export class SearchService {
private static readonly apiUrl: URL = new URL(‘http://api.loklak.org/api/search.json’);
private static maximum_records_fetch = 20;
private static minified_results = true;
private static source = ‘all’;
private static fields = ‘created_at,screen_name,mentions,hashtags’;
private static limit = 10;
private static timezoneOffset: string = new Date().getTimezoneOffset().toString();constructor(
private jsonp: Jsonp
) { }// TODO: make the searchParams as configureable model rather than this approach.
public fetchQuery(query: string, lastRecord = 0): Observable<ApiResponse> {
const searchParams = new URLSearchParams();
searchParams.set(‘q’, query);
searchParams.set(‘callback’, ‘JSONP_CALLBACK’);
searchParams.set(‘minified’, SearchService.minified_results.toString());
searchParams.set(‘source’, SearchService.source);
searchParams.set(‘maximumRecords’, SearchService.maximum_records_fetch.toString());
searchParams.set(‘timezoneOffset’, SearchService.timezoneOffset);
searchParams.set(‘startRecord’, (lastRecord + 1).toString());
searchParams.set(‘fields’, SearchService.fields);
searchParams.set(‘limit’, SearchService.limit.toString());
return this.jsonp.get(SearchService.apiUrl.toString(), { search: searchParams })
.map(this.extractData)}private extractData(res: Response): ApiResponse {
try {
return <ApiResponse>res.json();
} catch (error) {
console.error(error);
}
}

Testing the service

  • Create a mock backend to assure that we are not making any Jsonp request. We need to use Mock Jsonp provider for this. This provider sets up MockBackend and wires up all the dependencies to override the Request Options used by the JSONP request.

const mockJsonpProvider = {
provide: Jsonp,
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
return new Jsonp(backend, defaultOptions);
}
};

 

  • Now, we need to configure the testing module to isolate service from other dependencies. With this, we can instantiate services manually. We have to use TestBed for unit testing and provide all necessary imports/providers for creating and testing services in the unit test.

describe(‘Service: Search’, () => {
let service: SearchService = null;
let backend: MockBackend = null;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
MockBackend,
BaseRequestOptions,
mockJsonpProvider,
SearchService
]
});
});

 

  • Now, we will inject Service (to be tested) and MockBackend into the Testing module. As all the dependencies are injected, we can now initiate the connections and start testing the service.

beforeEach(inject([SearchService, MockBackend], (searchService: SearchService, mockBackend: MockBackend) => {
service = searchService;
backend = mockBackend;
}));

 

  • We will be using it() block to mention about what property/feature we are going to test in the block. All the tests will be included in this block. One of the most important part is to induce callback function done which will close the connection as soon the testing is over.

it(‘should call the search api and return the search results’, (done)=>{
// test goes here
});

 

  • Now, we will create a connection to the MockBackend and subscribe to this connection. We need to configure ResponseOptions so that mock response is JSONified and returned when the request is made.  Now, the MockBackend is set up and we can proceed to make assertions and test the service.

const result = MockResponse;
backend.connections.subscribe((connection: MockConnection) => {
const options = new ResponseOptions({
body: JSON.stringify(result)
});
connection.mockRespond(new Response(options));

 

  • We can now add test by using expect() block to check if the assertion is true or false. We will now test:
    • Request method: We will be testing if the request method used by the connection created is GET.

expect(connection.request.method).toEqual(RequestMethod.Get);
    • Request Url: We will be testing if all the URL Search Parameters are correct and according to what we provide as a parameter to the method fetchQuery().

expect(connection.request.url).toEqual(
`http://api.loklak.org/api/search.json` +
`?q=${query}` +
`&callback=JSONP_CALLBACK` +
`&minified=true&source=all` +
`&maximumRecords=20&timezoneOffset=${timezoneOffset}` +
`&startRecord=${lastRecord + 1}` +
`&fields=created_at,screen_name,mentions,hashtags&limit=10`);
});
);

 

  • Response:  Now, we need to call the service to make a request to the backend and subscribe to the response returned. Next, we will make an assertion to check if the response returned and parsed by the service is equal the Mock Response that should be returned. At the end, we need to call the callback function done() to close the connection.

service
.fetchQuery(query, lastRecord)
.subscribe((res) => {
expect(res).toEqual(result);
done();
});
});

Reference

Continue ReadingAdding Unit Tests for Services in loklak search

Auto-Refreshing Mode in loklak Media Wall

Auto-refreshing wall means that the request to the loklak server for the feeds must be sent after every few seconds and adding up new feeds in the media wall as soon as the response is received for a single session. For a nice implementation, it is also necessary to check if the new feeds are being received from the server and consequently, close the connection as soon as no feeds are received as to maintain session singularity.

In this blog post, I am explaining how I implemented the auto-refreshing mode for media wall using tools like ngrx/store and ngrx/effects.

Flow Chart

The flowchart below explains the workflow of how the actions, effects and service are linked to create a cycle of events for auto-refreshing mode. It also shows up how the response is handled as a dependency for the next request. Since effects play a major role for this behaviour, we can say it as the “Game of Effects”.

Working

  • Effect wallSearchAction$: Assuming the Query for media wall has changed and ACTION: WALL_SEARCH has been dispatched, we will start from this point of time. Looking into the flowchart, we can see as soon the action WALL_SEARCH is dispatched, a effect needs to be created to detect the action dispatched.This effect customizes the query and sets up various configurations for search service and calls the service. Depending on whether the response is received or not, it either dispatches WallSearchCompleteSuccessAction or WallSearchCompleteFailAction respectively. Moreover, this effect is responsible for changing the route/location of the application.

@Effect()
wallSearchAction$: Observable<Action>
= this.actions$
.ofType(wallAction.ActionTypes.WALL_SEARCH)
.debounceTime(400)
.map((action: wallAction.WallSearchAction) => action.payload)
.switchMap(query => {
const nextSearch$ = this.actions$.ofType(wallAction.ActionTypes.WALL_SEARCH).skip(1);
const searchServiceConfig: SearchServiceConfig = new SearchServiceConfig();if (query.filter.image) {
searchServiceConfig.addFilters([‘image’]);
} else {
searchServiceConfig.removeFilters([‘image’]);
}
if (query.filter.video) {
searchServiceConfig.addFilters([‘video’]);
} else {
searchServiceConfig.removeFilters([‘video’]);
}return this.apiSearchService.fetchQuery(query.queryString, searchServiceConfig)
.takeUntil(nextSearch$)
.map(response => {
const URIquery = encodeURIComponent(query.queryString);
this.location.go(`/wall?query=${URIquery}`);
return new apiAction.WallSearchCompleteSuccessAction(response);
})
.catch(() => of(new apiAction.WallSearchCompleteFailAction()));
  • Property lastResponseLength: Looking into the flow chart, we can see that after WallSearchCompleteSuccessAction is dispatched, we need to check for the number of feeds in the response. If the number of feeds in the response is more than 0, we can continue to make a new request to the server. On the other hand, if no feeds are received, we need to close the connection and stop requesting for more feeds. This check is implemented using lastResponseLength state property of the reducer which maintains the length of the entities for the last response received.

case apiAction.ActionTypes.WALL_SEARCH_COMPLETE_SUCCESS: {
const apiResponse = action.payload;return Object.assign({}, state, {
entities: apiResponse.statuses,
lastResponseLength: apiResponse.statuses.length
});
}

 

  • Effect nextWallSearchAction$: Now, we have all the information regarding if we should dispatch WALL_NEXT_PAGE_ACTION depending on the last response received. We need to implement an effect that detects WALL_SEARCH_COMPLETE_SUCCESS  keeping in mind that the next request should be made 10 seconds after the previous response is received. For this behaviour, we need to use debounceTime() which emits a value only after certain specified time period has passed. Here, debounce is set to 10000ms which is equal to 10 seconds. The effect also needs to dispatch the next action depending on the lastResponseLength state property of the reducer. It should dispatch WallNextPageAction if the entities length of the response is more than 0, otherwise, it should dispatch StopWallPaginationAction.

@Effect()
nextWallSearchAction$
= this.actions$
.ofType(apiAction.ActionTypes.WALL_SEARCH_COMPLETE_SUCCESS)
.debounceTime(10000)
.withLatestFrom(this.store$)
.map(([action, state]) => {
if (state.mediaWallResponse.lastResponseLength > 0) {
return new wallPaginationAction.WallNextPageAction();
}
else {
return new wallPaginationAction.StopWallPaginationAction();
}
});

 

  • Effect wallPagination$: Now, we need to have an effect that should detect WALL_NEXT_PAGE_ACTION and call the SearchService similar to wallSearchAction$ Effect. However, we need to keep a check on the last record of the entities from the previous response received. This can be done using lastRecord state property which maintains the last record of the entities.

@Effect()
wallPagination$: Observable<Action>
= this.actions$
.ofType(wallPaginationAction.ActionTypes.WALL_NEXT_PAGE)
.map((action: wallPaginationAction.WallNextPageAction) => action.payload)
.withLatestFrom(this.store$)
.map(([action, state]) => {
return {
query: state.mediaWallQuery.query,
lastRecord: state.mediaWallResponse.entities.length
};
})
.switchMap(queryObject => {
const nextSearch$ = this.actions$.ofType(wallAction.ActionTypes.WALL_SEARCH);this.searchServiceConfig.startRecord = queryObject.lastRecord + 1;
if (queryObject.query.filter.image) {
this.searchServiceConfig.addFilters([‘image’]);
} else {
this.searchServiceConfig.removeFilters([‘image’]);
}
if (queryObject.query.filter.video) {
this.searchServiceConfig.addFilters([‘video’]);
} else {
this.searchServiceConfig.removeFilters([‘video’]);
}return this.apiSearchService.fetchQuery(queryObject.query.queryString, this.searchServiceConfig)
.takeUntil(nextSearch$)
.map(response => {
return new wallPaginationAction.WallPaginationCompleteSuccessAction(response);
})
.catch(() => of(new wallPaginationAction.WallPaginationCompleteFailAction()));
});

 

  • Effect nextWallPageAction$: Similar to the nextWallSearchAction$ effect, we need to implement an effect that detects WALL_PAGINATION_SUCCESS_ACTION and depending on the lastResponseLength should either dispatch WallNextPageAction or StopWallPaginationAction after a certain specified debounceTime.

@Effect()
nextWallPageAction$
= this.actions$
.ofType(wallPaginationAction.ActionTypes.WALL_PAGINATION_COMPLETE_SUCCESS)
.debounceTime(10000)
.withLatestFrom(this.store$)
.map(([action, state]) => {
if (state.mediaWallResponse.lastResponseLength > 0) {
return new wallPaginationAction.WallNextPageAction();
}
else {
return new wallPaginationAction.StopWallPaginationAction();
}
});

 

Now the cycle is created and requests will be automatically made after every 10 seconds depending on the previous response. This cycle also closes the connection and stops making a pagination request for the particular query as soon as no feeds are received from the server.

Reference

Continue ReadingAuto-Refreshing Mode in loklak Media Wall

Query Model Structure of Loklak Search

Need to restructure

The earlier versions of loklak search applications had the issues of breaking changes whenever any new feature was added in the application. The main reason for these unintended bugs was identified to be the existing query structure. The query structure which was used in the earlier versions of the application only comprised of the single entity a string named as queryString.

export interface Query {
 queryString: string;
}

This simple query string property was good enough for simple string based searches which were the goals of the application in the initial phases, but as the application progressed we realized this simple string based implementation is not going to be feasible for the long term. As there are only a limited things we can do with strings. It becomes extremely difficult to set and reset the portions of the string according to the requirements. This was the main vision for the alternate architecture design scheme was to the ease of enabling and disabling the features on the fly.

Application Structure

Therefore, to overcome the difficulties faced with the simple string based structure we introduced the concept of an attribute based structure for queries. The attribute based structure is simpler to understand and thus easier to maintain the state of the query in the application.

export interface Query {
 displayString: string;
 queryString: string;
 routerString: string;
 filter: FilterList;
 location: string;
 timeBound: TimeBound;
 from: boolean;
}

The reason this is called an attribute based structure is that here each property of an interface is independent of one another. Thus each one can be thought of as a separate little key placed on the query, but each of these keys are different and are mutually exclusive. What this means is, if I want to write an attribute query, then it does not matter to me which other attributes are already present on the query. The query will eventually be processed and sent to the server and the corresponding valid results if exists will be shown to the user.

Now the question arises how do we modify the values of these attributes? Now before answering this I would like to mention that this interface is actually instantiated in the the Redux state, so now our question automatically gets answered, the modification to redux state corresponding to the query structure will be done by specific reducers meant for modification of each attribute. These reducers are again triggered by corresponding actions.

export const ActionTypes = {
 VALUE_CHANGE: '[Query] Value Change',
 FILTER_CHANGE: '[Query] Filter Change',
 LOCATION_CHANGE: '[Query] Location Change',
 TIME_BOUND_CHANGE: '[Query] Time Bound Change',
};

This ActionTypes object contains the the corresponding actions which are used to trigger the reducers. These actions can be dispatched in response to any user interaction by any of the components, thus modifying a particular state attribute via the reducer.

Converting from object to string

Now for our API endpoint to understand our query we need to send the proper string in API accepted format. For this there is need to convert dynamically from query state to query string, for this we need a simple function which take in query state as an input return the query string as output.

export function parseQueryToQueryString(query: Query): string {
 let qs: string;
 qs = query.displayString;
 if (query.location) {
qs += ` near:${query.location);

 if (query.timeBound.since) {
   qs += ` since:${parseDateToApiAcceptedFormat(query.timeBound.since)}`;

 if (query.timeBound.until) {
   qs += ` until:${parseDateToApiAcceptedFormat(query.timeBound.until)}`;

 return qs;
}

In this function we are just checking and updating the query string according to the various attributes set in the structure, and then returning the query string. So if eventually we have to convert to the string, then what is the advantage of this approach? The main advantage of this approach is that we know the query structure beforehand and we use the structure to build the string not just randomly selecting and removing pieces of information from a string. Whenever we update any of the attribute of the query state, the query is generated fresh, and not modifying the old string.

Conclusion

This approach makes the application to be able to modify the search queries sent to server in a streamlined and logical way, just by using simple data structure. This query model has provided us with a lot of advantages which are visible in the aspect of application stability and performance. This model has cuts out dirty regex matching, of typed queries and thus again help us to make simpler queries.

Resources and Links

Continue ReadingQuery Model Structure of Loklak Search

Adding unit tests for effects in Loklak Search

Loklak search uses @ngrx/effects to listen to actions dispatched by the user and sending API request to the loklak server. Loklak search, currently, has seven effects such as Search Effects,  Suggest Effects which runs to make the application reactive. It is important to test these effects to ensure that effects make API calls at the right time and then map the response to send it back to the reducer.

I will  explain here how I added unit tests for the effects. Surprisingly, the test coverage increased from 43% to 55% after adding these tests.

Effects to test

We are going to test effects for user search. This effect listens to the event of type USER_SEARCH and makes a call to the user-search service with the query as a parameter. After a response is received, it maps the response and passes it on the UserSearchCompleteSuccessAction action which performs the later operation. If the service fails to get a response, it makes a call to the UserSearchCompleteFailAction.

Code

ApiUserSearchEffects is the effect which detects if the USER_SEARCH action is dispatched from some component of the application and consequently, it makes a call to the UserSearchService and handles the JSON response received from the server. The effects then, dispatch the action new UserSearchCompleteSuccessAction if response is received from server or either dispatch the action new UserSearchCompleteFailAction if no response is received. The debounce time is set to 400 so that response can be flushed if a new USER_SEARCH is dispatched within the next 400ms.

For this effect, we need to test if the effects actually runs when USER_SEARCH action is made. Further, we need to test if the correct parameters are supplied to the service and response is handled carefully. We also, need to check if the response if really flushed out within the certain debounce time limit.

@Injectable()
export class ApiUserSearchEffects {@Effect()
search$: Observable<Action>
= this.actions$
.ofType(userApiAction.ActionTypes.USER_SEARCH)
.debounceTime(400)
.map((action: userApiAction.UserSearchAction) => action.payload)
.switchMap(query => {
const nextSearch$ = this.actions$.ofType(userApiAction.ActionTypes.USER_SEARCH).skip(1);const follow_count = 10;return this.apiUserService.fetchQuery(query.screen_name, follow_count)
.takeUntil(nextSearch$)
.map(response => new userApiAction.UserSearchCompleteSuccessAction(response))
.catch(() => of(new userApiAction.UserSearchCompleteFailAction()));
});constructor(
private actions$: Actions,
private apiUserService: UserService
) { }

}

Unit test for effects

  • Configure the TestBed class before starting the unit test and add all the necessary imports (most important being the EffectsTestingModule) and providers. This step will help to isolate the effects completely from all other components and testing it independently. We also need to create spy object which spies on the method userService.fetchQuery with provider being UserService.

beforeEach(() => TestBed.configureTestingModule({
imports: [
EffectsTestingModule,
RouterTestingModule
],
providers: [
ApiUserSearchEffects,
{
provide: UserService,
useValue: jasmine.createSpyObj(‘userService’, [‘fetchQuery’])
}
]
}));
  • Now, we will be needing a function setup which takes params which are the data to be returned by the Mock User Service. We can now configure the response to returned by the service. Moreover, this function will be initializing EffectsRunner and returning ApiUserSearchEffects so that it can be used for unit testing.

function setup(params?: {userApiReturnValue: any}) {
const userService = TestBed.get(UserService);
if (params) { userService.fetchQuery.and.returnValue(params.userApiReturnValue);
}return {
runner: TestBed.get(EffectsRunner),
apiUserSearchEffects: TestBed.get(ApiUserSearchEffects)
};
}

 

  • Now we will be adding unit tests for the effects. In these tests, we are going to test if the effects recognise the action and return some new action based on the response we want and if it maps the response only after a certain debounce time.We have used fakeAsync() which gives us access to the tick() function. Next, We are calling the function setup and pass on the Mock Response so that whenever User Service is called it returns the Mock Response and never runs the service actually. We will now queue the action UserSearchAction in the runner and subscribe to the value returned by the effects class. We can now test the value returned using expect() block and that the value is returned only after a certain debounce time using tick() block.

it(‘should return a new userApiAction.UserSearchCompleteSuccessAction, ‘ +
‘with the response, on success, after the de-bounce’, fakeAsync(() => {
const response = MockUserResponse;const {runner, apiUserSearchEffects} = setup({userApiReturnValue: Observable.of(response)});

const expectedResult = new userApiAction.UserSearchCompleteSuccessAction(response);

runner.queue(new userApiAction.UserSearchAction(MockUserQuery));

let result = null;
apiUserSearchEffects.search$.subscribe(_result => result = _result);
tick(399); // test debounce
expect(result).toBe(null);
tick(401);
expect(result).toEqual(expectedResult);
}));

it(‘should return a new userApiAction.UserSearchCompleteFailAction,’ +
‘if the SearchService throws’, fakeAsync(() => {
const { runner, apiUserSearchEffects } = setup({ userApiReturnValue: Observable.throw(new Error()) });

const expectedResult = new userApiAction.UserSearchCompleteFailAction();

runner.queue(new userApiAction.UserSearchAction(MockUserQuery));

let result = null;
apiUserSearchEffects.search$.subscribe(_result => result = _result );

tick(399); // Test debounce
expect(result).toBe(null);
tick(401);
expect(result).toEqual(expectedResult);
}));
});

Reference

Continue ReadingAdding unit tests for effects in Loklak Search

Introducing Customization in Loklak Media Wall

My GSoC Project includes implementing media wall in loklak search . One part of the issue is to include customization options in media wall. I looked around for most important features that can be implemented in a media wall to give the user a more appealing and personalized view. One of the feature that can be implemented is enabling Full Screen Mode.  This feature can help the user to display media wall on the projector or any big display screen without compromising with space available. In one part, I would be explaining how I implemented Full screen Mode in loklak media wall using fullscreen.js library.

Secondly, it is important to include a very reactive and user-friendly setting box. The setting box should be a central container in which all the customization options will be included.In loklak media wall,  setting box is implemented as a dialog box with various classifications in form of tabs. I would also be explaining  how I designed customization menu using Angular Material.

Implementation

Full Screen Mode

Since loklak search is an Angular 2 application and all the code is written in typescript, we can’t simply use the fullscreen.js library. We have to import the library into our application and create a directive that can be applied to the element to use it in the application.

  • Install fullscreen.js library in the application using Node Package Manager.

npm install save screenfull

import {Directive, HostListener, Output, EventEmitter} from ‘@angular/core’;
import * as screenfull from ‘screenfull’;@Directive({
selector: ‘[toggleFullscreen]’
})
export class ToggleFullscreenDirective {constructor() {}@HostListener(‘click’) onClick() {
if (screenfull.enabled) {
screenfull.toggle();
}
}
}
  • Import Directive into the module and add it to declaration. This allows directive to be used anywhere in the template.

import { ToggleFullscreenDirective } from ‘../shared//full-screen.directive’;
.
.
.
@NgModule({
.
.
.
declarations: [
.
.
.
ToggleFullscreenDirective,
.
.
.
]
})
export class MediaWallModule { }
  • Now, the directive is ready to use on the template. We just have to add this attribute directive to an element.

<i toggleFullscreen mdTooltip=“Full Screen” mdTooltipPosition=“below” class=“material-icons md-36”>fullscreen</i>

Customization Menu

Customization Menu is created using the idea of central container for customization. It is created using two components of Angular Material – Dialog Box and Tabs . We will now be looking how customization menu is implemented using these two components.

  • Create a component with the pre-configured position, height and width of the dialog box. This can be done simply using updatePosition and updateSize property of the MdDialogRef class.

export class MediaWallCustomizationComponent implements OnInit {
public query: string;constructor(
private dialogRef: MdDialogRef<MediaWallCustomizationComponent>,
private store: Store<fromRoot.State>,
private location: Location) { }ngOnInit() {
this.dialogRef.updatePosition(’10px’);
this.dialogRef.updateSize(‘80%’, ‘80%’);
}public searchAction() {
if (this.query) {
this.store.dispatch(new mediaWallAction.WallInputValueChangeAction(this.query));
this.location.go(‘/wall’, `query=${this.query}`);
}
}
}
  • Create a template for the Customization menu. We will be using md-tab and md-dialog to create a dialog box with options displayed using tabs. dynamicHeight should be set to true so that dialog box adjust according to the tabs. We can simply add an attribute md-dialog-close to the button which will close the dialog box. All the content should be added in the div with attribute md-dialog-content linked to it. Moreover, to make options look more user-friendly and adjustable on smaller screens, icons must be added with the Tab title.

<h1 mddialogtitle>Customization Menu</h1>
<button class=“form-close” mddialogclose>x</button>
<span mddialogcontent>
<mdtabgroup color=“accent” dynamicHeight=“true”>
<mdtab>
<ngtemplate mdtablabel>
<mdicon>search</mdicon>
Search For
</ngtemplate>
<h3> Search Customization </h3>
<mdinputcontainer class=“example-full-width” color=“accent”>
<input placeholder=“Search Term” mdInput type =“text” class=“input” name=“search-term” [(ngModel)]=“query”>
</mdinputcontainer>
<span class=“apply-button”>
<button mdraisedbutton color=“accent” mddialogclose (click)=“searchAction()”>Display</button>
</span>
</mdtab>
</mdtabgroup>
</span>

The code currently shows up code for search customization. It basically, records to the input using [(ngModel)] for two-way binding and makes the call the search action whenever user clicks on Display button.

  • Add a button which would open dialog box using open property of MdDialog class. This property would provide an instance for MediaWallCustomizationComponent and the component will show up dynamically.

<i class=“material-icons md-36” (click)=“dialog.open(MediaWallCustomizationComponent)”>settings</i>
  • It is important to add MediaWallCustomizationComponent as an entry component in the module so that AOT compiler can create a ComponentFactory for it during initialization.

import { MediaWallCustomizationComponent } from ‘./media-wall-customization/media-wall-customization.component’;

@NgModule({
entryComponents: [
MediaWallCustomizationComponent
]
})
export class MediaWallModule { }

 

This creates an appealing and user-friendly customization menu which acts a central container for customization options.

References

Continue ReadingIntroducing Customization in Loklak Media Wall