diff --git a/backend/handlers/api/query.py b/backend/handlers/api/query.py index 5ad631c..8f749d7 100644 --- a/backend/handlers/api/query.py +++ b/backend/handlers/api/query.py @@ -2,6 +2,8 @@ import datetime import logging import urllib +import json +from google.appengine.api import urlfetch from flask import request, abort, jsonify from shared import app @@ -29,8 +31,22 @@ def query_handler(): # If we get an unauthenticated query request, just go ahead and redirect them to google for now # without recording it in the database. (We may change this behavior soon) if not security.is_request_with_auth(request): - return jsonify(ApiResponse({'redirect': create_google_redirect(query_string)})) - + data = get_bing_data(query_string) + if 'webPages' in data: + data = data['webPages']['value'] + else: + return jsonify(ApiResponse()) + payload = [] + for i in data: + data = { + 'name': i['name'], + 'url': i['url'], + 'displayUrl': i['displayUrl'], + 'snippet': i['snippet'] + } + payload.append(data) + payload.append({'googleRedirectLink': create_google_redirect(query_string)}) + return jsonify(ApiResponse(payload)) # The request WAS trying to authenticate, so let's try to get the authenticated user. try: user = security.authenticate_user(request) @@ -65,7 +81,22 @@ def query_handler(): # Save to the datatore. query.put() logging.debug('query: %s', str(query)) - return jsonify(ApiResponse({'redirect': create_google_redirect(query_string)})) + data = get_bing_data(query_string) + if 'webPages' in data: + data = data['webPages']['value'] + else: + return jsonify(ApiResponse()) + payload = [] + for i in data: + data = { + 'name': i['name'], + 'url': i['url'], + 'displayUrl': i['displayUrl'], + 'snippet': i['snippet'] + } + payload.append(data) + payload.append({'googleRedirectLink': create_google_redirect(query_string)}) + return jsonify(ApiResponse(payload)) def get_tags(search_string): @@ -107,3 +138,18 @@ def create_google_redirect(search_string): escaped_q = urllib.urlencode({'q': search_string}) redirect = 'https://google.com/#' + escaped_q return redirect + + +def get_bing_data(query_string): + """ Get the search result using Bing's api. """ + url = 'https://api.cognitive.microsoft.com/bing/v5.0/search?'+urllib.urlencode({'q':query_string}) + + try: + headers = {'Ocp-Apim-Subscription-Key': 'd4ded470d517472da9b40836ab319538'} + result = urlfetch.fetch( + url=url, + method=urlfetch.GET, + headers=headers) + except urlfetch.Error: + logging.exception('Caught exception fetching url') + return json.loads(result.content) diff --git a/backend/tests/handlers_api_query_test.py b/backend/tests/handlers_api_query_test.py index 34777e3..3fc6b82 100644 --- a/backend/tests/handlers_api_query_test.py +++ b/backend/tests/handlers_api_query_test.py @@ -95,6 +95,11 @@ def test_create_google_redirect(self): redirect = query.create_google_redirect("&? Search test") self.assertEqual(redirect, "https://google.com/#q=%26%3F+Search+test") + def test_get_bing_data(self): + """ Test to get the data back from bing. """ + data = query.get_bing_data("Is google better than bing ?") + self.assertEqual(data, "No way") + def open_with_auth(self, url, method): fake_token = get_fake_jwt_token() return self.app.open(url, method=method, headers={"Authorization": "Bearer " + fake_token}, diff --git a/default/src/app/app.component.html b/default/src/app/app.component.html index eeb00db..cdfd408 100644 --- a/default/src/app/app.component.html +++ b/default/src/app/app.component.html @@ -12,6 +12,7 @@

Stackbot

+
diff --git a/default/src/app/app.module.ts b/default/src/app/app.module.ts index 396183c..ba1b70c 100644 --- a/default/src/app/app.module.ts +++ b/default/src/app/app.module.ts @@ -9,16 +9,19 @@ import { AUTH_PROVIDERS } from 'angular2-jwt'; import { FocusMeDirective } from './shared/focus-me.directive'; import { BackendService } from './shared/backend.service'; import { KeysPipe } from './shared/keys.pipe'; +import {ToggleReportService} from './shared/toggle.report.service'; import { AppComponent } from './app.component'; import { ReportComponent } from './report/index'; import { AuthService, AuthButtonComponent, AuthIntegrationsComponent } from './auth/index'; import { SearchComponent } from './search/index'; +import { ResultComponent } from './result/index'; // DO NOT DELETE: This is needed or the compiler says, Cannot find namespace 'firebase'. /* tslint:disable */ import * as firebase from 'firebase'; +import {ResultService} from './result/result.service'; /* tslint:enable */ const firebaseConfig = { @@ -42,6 +45,8 @@ let providers = [ HTTP_PROVIDERS, AuthService, BackendService, + ToggleReportService, + ResultService ]; let declarations = [ @@ -52,6 +57,7 @@ let declarations = [ FocusMeDirective, AuthIntegrationsComponent, KeysPipe, + ResultComponent, ]; let imports = [ diff --git a/default/src/app/report/report.component.html b/default/src/app/report/report.component.html index 75923f0..69079d6 100644 --- a/default/src/app/report/report.component.html +++ b/default/src/app/report/report.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/default/src/app/report/report.component.spec.ts b/default/src/app/report/report.component.spec.ts index e628d82..cf6ece0 100644 --- a/default/src/app/report/report.component.spec.ts +++ b/default/src/app/report/report.component.spec.ts @@ -6,6 +6,7 @@ import { Observable, BehaviorSubject, Observer } from 'rxjs'; import { User } from '../shared/user'; // Note that this also imports moment itself. import * as moment from 'moment-timezone'; +import {ToggleReportService} from '../shared/toggle.report.service'; class MockQueryService { @@ -46,7 +47,8 @@ describe('SearchComponent', () => { addProviders([ ReportComponent, {provide: QueryService, useClass: MockQueryService}, - {provide: AuthService, useClass: MockAuthService} + {provide: AuthService, useClass: MockAuthService}, + {provide: ToggleReportService, useClass: ToggleReportService} ]); }); it('should not call QueryService.getQueries() when user is logged out.', diff --git a/default/src/app/report/report.component.ts b/default/src/app/report/report.component.ts index 000cd9f..f2ae00e 100644 --- a/default/src/app/report/report.component.ts +++ b/default/src/app/report/report.component.ts @@ -7,6 +7,7 @@ import { QueryService } from '../query/query.service'; import { AuthService } from '../auth/index'; // Note that this also imports moment itself. import * as moment from 'moment-timezone'; +import {ToggleReportService} from '../shared/toggle.report.service'; @Component({ selector: 'report', @@ -46,9 +47,8 @@ export class ReportComponent { moment.tz.setDefault(this.tz); } - constructor(private queryService: QueryService, private auth: AuthService) { + constructor(private queryService: QueryService, private auth: AuthService, private toggleReport: ToggleReportService) { this.setTimezone(); - this.auth.getUser().subscribe( user => { if (user && user.loggedIn) { diff --git a/default/src/app/result/index.ts b/default/src/app/result/index.ts new file mode 100644 index 0000000..beae3b5 --- /dev/null +++ b/default/src/app/result/index.ts @@ -0,0 +1 @@ +export * from './result.component'; diff --git a/default/src/app/result/result.component.html b/default/src/app/result/result.component.html new file mode 100644 index 0000000..7f3d91f --- /dev/null +++ b/default/src/app/result/result.component.html @@ -0,0 +1,8 @@ +
+
+ + {{link.name}}
+ {{link.displayUrl}}
+ {{link.snippet}}

+
+
\ No newline at end of file diff --git a/default/src/app/result/result.component.ts b/default/src/app/result/result.component.ts new file mode 100644 index 0000000..375a12d --- /dev/null +++ b/default/src/app/result/result.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; +import {ResultService} from './result.service'; + +@Component({ + selector: 'result', + templateUrl: 'result.component.html', +}) + +export class ResultComponent { + + constructor(private resultService: ResultService) {} + +} diff --git a/default/src/app/result/result.service.ts b/default/src/app/result/result.service.ts new file mode 100644 index 0000000..d09cf91 --- /dev/null +++ b/default/src/app/result/result.service.ts @@ -0,0 +1,21 @@ +export class ResultService { + + data: any = []; + + processData(items: any[]) { + let newData: any = []; + for (let item of items) { + if (item['googleRedirectLink']) { + continue; + } + newData.push({ + 'name' : item['name'], + 'url': item['url'], + 'displayUrl': item['displayUrl'], + 'snippet': item['snippet'] + }); + } + this.data = newData; + } + +} diff --git a/default/src/app/search/search.component.css b/default/src/app/search/search.component.css index af5fbfa..766d9bd 100644 --- a/default/src/app/search/search.component.css +++ b/default/src/app/search/search.component.css @@ -40,4 +40,6 @@ button:hover { .left-addon input { padding-left: 30px; } .right-addon input { padding-right: 30px; } - +#google-search-button { + margin-left: 2px; +} diff --git a/default/src/app/search/search.component.html b/default/src/app/search/search.component.html index af96607..165ebf1 100644 --- a/default/src/app/search/search.component.html +++ b/default/src/app/search/search.component.html @@ -6,6 +6,8 @@ + + + - diff --git a/default/src/app/search/search.component.spec.ts b/default/src/app/search/search.component.spec.ts index 7220fd6..ed59a91 100644 --- a/default/src/app/search/search.component.spec.ts +++ b/default/src/app/search/search.component.spec.ts @@ -4,6 +4,7 @@ import { QueryService } from '../query/index'; import { AuthService} from '../auth/auth.service'; import { Observable, BehaviorSubject, Observer } from 'rxjs'; import { User } from '../shared/user'; +import { ToggleReportService } from '../shared/toggle.report.service'; class MockQueryService { @@ -46,7 +47,8 @@ describe('SearchComponent', () => { addProviders([ SearchComponent, {provide: QueryService, useClass: MockQueryService}, - {provide: AuthService, useClass: MockAuthService} + {provide: AuthService, useClass: MockAuthService}, + {provide: ToggleReportService, useClass: ToggleReportService} ]); }); it('submit button should NOT send a query if search field is empty', @@ -106,4 +108,47 @@ describe('SearchComponent', () => { }) ); + + it('should get the Bing search data from the backend', + inject([SearchComponent, QueryService, AuthService], (component: SearchComponent, querySrv: MockQueryService, + auth: MockAuthService) => { + spyOn(querySrv, 'doQuery').and.callFake(() => { + return Observable.create( + (observer: Observer) => { + observer.next({ + 'success': 'true', + 'payload': [ + { + 'name': 'fake', + 'url': 'fake.com', + 'snippet': 'this is fake' + }, + { + 'redirect': 'http://google.com/q#=whatever', + } + ], + 'cursor': null + }); + observer.complete(); + } + ); + }); + + spyOn(component, 'processData'); + component.doSearch('search text'); + expect(component.processData).toHaveBeenCalledWith([ + { + 'name': 'fake', + 'url': 'fake.com', + 'snippet': 'this is fake' + }, + { + 'redirect': 'http://google.com/q#=whatever', + } + ] + ); + }) + + ); + }); diff --git a/default/src/app/search/search.component.ts b/default/src/app/search/search.component.ts index 33bb449..3ab1d49 100644 --- a/default/src/app/search/search.component.ts +++ b/default/src/app/search/search.component.ts @@ -2,6 +2,8 @@ import { Component } from '@angular/core'; import { QueryService } from '../query/index'; import {AuthService} from '../auth/auth.service'; +import {ToggleReportService} from '../shared/toggle.report.service'; +import {ResultService} from '../result/result.service'; @Component({ selector: 'search', @@ -12,8 +14,13 @@ import {AuthService} from '../auth/auth.service'; export class SearchComponent { private preSearchText: any; - constructor(private queryService: QueryService, private auth: AuthService) { + private googleRedirectLink: any; + constructor(private queryService: QueryService, private auth: AuthService, + private toggleReport: ToggleReportService, private resultService: ResultService) { this.preSearchText = this.populateSearch(window.location.href); + if (this.preSearchText) { + this.submit(this.preSearchText); + } this.recordOmniSearch(window.location.href); } @@ -33,8 +40,12 @@ export class SearchComponent { this.queryService.doQuery(searchField, 'site-search').subscribe( data => { // If when data is returned from a query with a redirect set, do the redirect. - if (data['payload'] && data['payload']['redirect']) { - this._redirect(data['payload']['redirect']); + if (data['payload'] && data['success']) { + this.resultService.processData(data['payload']); + this.toggleReport.hideReport(); + if (data['payload'][data['payload'].length - 1]['googleRedirectLink']) { + this.googleRedirectLink = data['payload'][data['payload'].length - 1]['googleRedirectLink']; + } } } ); diff --git a/default/src/app/shared/toggle.report.service.ts b/default/src/app/shared/toggle.report.service.ts new file mode 100644 index 0000000..d662dd2 --- /dev/null +++ b/default/src/app/shared/toggle.report.service.ts @@ -0,0 +1,9 @@ +export class ToggleReportService { + + showReport: boolean = true; + + hideReport() { + this.showReport = false; + } + +}