Changeset 6092 for SRUAggregator


Ignore:
Timestamp:
03/10/15 14:16:28 (9 years ago)
Author:
emanuel.dima@uni-tuebingen.de
Message:
  1. beta-27: more results, bug fixes
Location:
SRUAggregator/trunk
Files:
1 deleted
12 edited

Legend:

Unmodified
Added
Removed
  • SRUAggregator/trunk/pom.xml

    r6081 r6092  
    88        <groupId>eu.clarin.sru.fcs</groupId>
    99        <artifactId>Aggregator2</artifactId>
    10         <version>2.0.0-alpha-26</version>
     10        <version>2.0.0-beta-27</version>
    1111        <name>FCS Aggregator</name>
    1212
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/app/Aggregator.java

    r6081 r6092  
    107107        private static final org.slf4j.Logger log = LoggerFactory.getLogger(Aggregator.class);
    108108
    109         public static final String DE_TOK_MODEL = "tokenizer/de-tuebadz-8.0-token.bin";
     109        final int SEARCHES_SIZE_GC_THRESHOLD = 1000;
     110        final int SEARCHES_AGE_GC_THRESHOLD = 60;
    110111
    111112        private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
     
    261262        // this function should be thread-safe
    262263        public Search startSearch(SRUVersion version, List<Corpus> corpora,
    263                         String searchString, String searchLang, int maxRecords) throws Exception {
     264                        String searchString, String searchLang,
     265                        int firstRecord, int maxRecords) throws Exception {
    264266                if (corpora.isEmpty()) {
    265267                        // No corpora
     
    270272                } else {
    271273                        Search sr = new Search(sruSearchClient, version, searchStatsAtom.get(),
    272                                         corpora, searchString, searchLang, 1, maxRecords);
    273                         if ((activeSearches.size() % 100) == 0) {
     274                                        corpora, searchString, searchLang, maxRecords);
     275                        if (activeSearches.size() > SEARCHES_SIZE_GC_THRESHOLD) {
    274276                                List<Long> toBeRemoved = new ArrayList<Long>();
    275277                                long t0 = System.currentTimeMillis();
    276278                                for (Map.Entry<Long, Search> e : activeSearches.entrySet()) {
    277                                         if (t0 - e.getValue().getCreatedAt() > 1800 * 1000) {
     279                                        long dtmin = (t0 - e.getValue().getCreatedAt()) / 1000 / 60;
     280                                        if (dtmin > SEARCHES_AGE_GC_THRESHOLD) {
     281                                                log.info("removing search " + e.getKey() + ": " + dtmin + " minutes old");
    278282                                                toBeRemoved.add(e.getKey());
    279283                                        }
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/rest/RestService.java

    r6081 r6092  
    11package eu.clarin.sru.fcs.aggregator.rest;
    22
     3import com.fasterxml.jackson.annotation.JsonProperty;
    34import com.fasterxml.jackson.core.JsonProcessingException;
    45import com.fasterxml.jackson.databind.ObjectMapper;
     
    1112import eu.clarin.sru.fcs.aggregator.scan.Corpus;
    1213import eu.clarin.sru.fcs.aggregator.scan.Statistics;
    13 import eu.clarin.sru.fcs.aggregator.search.Request;
    1414import eu.clarin.sru.fcs.aggregator.search.Result;
    1515import eu.clarin.sru.fcs.aggregator.search.Search;
     
    9898        public Response postSearch(
    9999                        @FormParam("query") String query,
     100                        @FormParam("firstResultIndex") Integer firstResultIndex,
    100101                        @FormParam("numberOfResults") Integer numberOfResults,
    101102                        @FormParam("language") String language,
    102                         @FormParam("useLanguageGuesser") boolean useLanguageGuesser,
    103103                        @FormParam("corporaIds[]") List<String> corporaIds) throws Exception {
    104104                if (query == null || query.isEmpty()) {
     
    113113                        return Response.status(503).entity("No corpora, please wait for the server to finish scanning").build();
    114114                }
     115
     116                if (firstResultIndex == null || firstResultIndex < 1) {
     117                        firstResultIndex = 1;
     118                }
     119                if (firstResultIndex > 250) {
     120                        firstResultIndex = 250;
     121                }
     122
    115123                if (numberOfResults == null || numberOfResults < 10) {
    116124                        numberOfResults = 10;
     
    119127                        numberOfResults = 250;
    120128                }
    121                 Search search = Aggregator.getInstance().startSearch(SRUVersion.VERSION_1_2, corpora, query, language, numberOfResults);
     129
     130                Search search = Aggregator.getInstance().startSearch(SRUVersion.VERSION_1_2,
     131                                corpora, query, language, firstResultIndex, numberOfResults);
    122132                if (search == null) {
    123133                        return Response.status(500).entity("Initiating search failed").build();
     
    129139        public static class JsonSearch {
    130140
    131                 List<Request> requests;
     141                @JsonProperty
     142                int inProgress = 0;
     143                @JsonProperty
    132144                List<Result> results;
    133145
    134                 public JsonSearch(List<Request> requests, List<Result> results) {
    135                         this.requests = requests;
     146                public JsonSearch(List<Result> results) {
    136147                        this.results = results;
    137                 }
    138 
    139                 public List<Request> getRequests() {
    140                         return requests;
    141                 }
    142 
    143                 public List<Result> getResults() {
    144                         return results;
    145148                }
    146149        }
     
    155158                }
    156159
    157                 JsonSearch js = new JsonSearch(search.getRequests(), search.getResults(corpusId));
     160                JsonSearch js = new JsonSearch(search.getResults(corpusId));
     161                for (Result r : js.results) {
     162                        if (r.getInProgress()) {
     163                                js.inProgress++;
     164                        }
     165                }
    158166                return Response.ok(js).build();
     167        }
     168
     169        @POST
     170        @Path("search/{id}")
     171        public Response postSearchNextResults(@PathParam("id") Long searchId,
     172                        @FormParam("corpusId") String corpusId,
     173                        @FormParam("numberOfResults") Integer numberOfResults) throws Exception {
     174                log.info("POST /search/{id}, corpusId: " + corpusId);
     175                if (corpusId == null || corpusId.isEmpty()) {
     176                        return Response.status(400).entity("'corpusId' parameter expected").build();
     177                }
     178                Search search = Aggregator.getInstance().getSearchById(searchId);
     179                if (search == null) {
     180                        return Response.status(Response.Status.NOT_FOUND).entity("Search job not found").build();
     181                }
     182                if (numberOfResults == null || numberOfResults < 10) {
     183                        numberOfResults = 10;
     184                }
     185                if (numberOfResults > 250) {
     186                        numberOfResults = 250;
     187                }
     188
     189                boolean ret = search.searchForNextResults(corpusId, numberOfResults);
     190                if (ret == false) {
     191                        return Response.status(500).entity("Initiating subSearch failed").build();
     192                }
     193                URI uri = URI.create("" + search.getId());
     194                return Response.created(uri).entity(uri).build();
    159195        }
    160196
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/Diagnostic.java

    r5971 r6092  
    2323        }
    2424
     25        public String getUri() {
     26                return uri;
     27        }
     28
     29        public String getMessage() {
     30                return message;
     31        }
     32
     33        public String getDiagnostic() {
     34                return diagnostic;
     35        }
     36
    2537        @Override
    2638        public int hashCode() {
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/scan/ScanCrawler.java

    r6081 r6092  
    225225                final Corpora corpora;
    226226                final int depth;
    227                 String fullRequestUrl;
    228227
    229228                ScanTask(final Institution institution, final Endpoint endpoint,
     
    248247                                scanRequest.setExtraRequestData(SRUCQL.SCAN_RESOURCE_INFO_PARAMETER,
    249248                                                SRUCQL.SCAN_RESOURCE_INFO_PARAMETER_DEFAULT_VALUE);
    250                                 fullRequestUrl = scanRequest.getRequestedURI().toString();
    251249                        } catch (Throwable ex) {
    252250                                log.error("Exception creating scan request for {}: {}", endpoint.getUrl(), ex.getMessage());
     
    285283                                        for (SRUDiagnostic d : response.getDiagnostics()) {
    286284                                                Diagnostic diag = new Diagnostic(d.getURI(), d.getMessage(), d.getDetails());
    287                                                 statistics.addEndpointDiagnostic(institution, endpoint, diag, fullRequestUrl);
    288                                                 log.info("Diagnostic: {}: {}", fullRequestUrl, diag.message);
     285                                                statistics.addEndpointDiagnostic(institution, endpoint, diag, response.getRequest().getRequestedURI().toString());
     286                                                log.info("Diagnostic: {}: {}", response.getRequest().getRequestedURI().toString(), diag.message);
    289287                                        }
    290288                                }
     
    294292                                log.error("{} Exception in scan callback {}#{}", latch.get(), endpoint.getUrl(), normalizeHandle(parentCorpus));
    295293                                log.error("--> ", xc);
    296                                 statistics.addErrorDatapoint(institution, endpoint, xc, fullRequestUrl);
     294                                statistics.addErrorDatapoint(institution, endpoint, xc, response.getRequest().getRequestedURI().toString());
    297295                        } finally {
    298296                                latch.decrement();
     
    305303                                log.error("{} Error while scanning {}#{}: {}", latch.get(), endpoint.getUrl(), normalizeHandle(parentCorpus), error.getMessage());
    306304                                statistics.addEndpointDatapoint(institution, endpoint, stats.getQueueTime(), stats.getExecutionTime());
    307                                 statistics.addErrorDatapoint(institution, endpoint, error, fullRequestUrl);
     305                                statistics.addErrorDatapoint(institution, endpoint, error, request.getRequestedURI().toString());
    308306                                if (Throw.isCausedBy(error, SocketTimeoutException.class)) {
    309307                                        return;
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Result.java

    r5971 r6092  
    11package eu.clarin.sru.fcs.aggregator.search;
    22
    3 import eu.clarin.sru.client.SRUClientException;
    43import eu.clarin.sru.client.SRUDiagnostic;
    54import eu.clarin.sru.client.SRURecord;
     
    1918import java.util.Collections;
    2019import java.util.List;
     20import java.util.concurrent.atomic.AtomicBoolean;
     21import java.util.concurrent.atomic.AtomicInteger;
     22import java.util.concurrent.atomic.AtomicReference;
    2123import org.w3c.dom.Node;
    2224import org.slf4j.LoggerFactory;
     
    3436        private static final org.slf4j.Logger log = LoggerFactory.getLogger(Result.class);
    3537
    36         private Request request;
    37         private List<Kwic> kwics = new ArrayList<Kwic>();
    38         private JsonException exception;
    39         private List<Diagnostic> diagnostics = new ArrayList<Diagnostic>();
     38        private final Corpus corpus;
     39        private AtomicBoolean inProgress = new AtomicBoolean(true);
     40        private AtomicInteger endpointReturnedRecords = new AtomicInteger();
     41        private AtomicReference<JsonException> exception = new AtomicReference<JsonException>();
     42        private List<Diagnostic> diagnostics = Collections.synchronizedList(new ArrayList<Diagnostic>());
     43        private List<Kwic> kwics = Collections.synchronizedList(new ArrayList<Kwic>());
    4044
    4145        public List<Kwic> getKwics() {
     
    4347        }
    4448
    45         public Result(Request request, SRUSearchRetrieveResponse response,
    46                         SRUClientException xc) {
    47                 this.request = request;
    48                 if (xc != null) {
    49                         exception = new JsonException(xc);
    50                 }
     49        public Result(Corpus corpus) {
     50                this.corpus = corpus;
     51        }
     52
     53        public void setInProgress(boolean inProgress) {
     54                this.inProgress.set(inProgress);
     55        }
     56
     57        public boolean getInProgress() {
     58                return inProgress.get();
     59        }
     60
     61        public void addResponse(SRUSearchRetrieveResponse response) {
    5162                if (response != null && response.hasRecords()) {
    52                         setResponse(response);
     63                        for (SRURecord record : response.getRecords()) {
     64                                addRecord(record);
     65                        }
    5366                }
    5467                if (response != null && response.hasDiagnostics()) {
    55                         setDiagnostics(response);
    56                 }
    57         }
    58 
    59         void setResponse(SRUSearchRetrieveResponse response) {
    60                 for (SRURecord record : response.getRecords()) {
    61                         if (record.isRecordSchema(ClarinFCSRecordData.RECORD_SCHEMA)) {
    62                                 ClarinFCSRecordData rd = (ClarinFCSRecordData) record.getRecordData();
    63                                 Resource resource = rd.getResource();
    64                                 setClarinRecord(resource);
    65                                 log.debug("Resource ref={0}, pid={1}, dataViews={2}",
    66                                                 new Object[]{resource.getRef(), resource.getPid(), resource.hasDataViews()});
    67                         } else if (record.isRecordSchema(SRUSurrogateRecordData.RECORD_SCHEMA)) {
    68                                 SRUSurrogateRecordData r = (SRUSurrogateRecordData) record.getRecordData();
    69                                 log.info("Surrogate diagnostic: uri={0}, message={1}, detail={2}",
    70                                                 new Object[]{r.getURI(), r.getMessage(), r.getDetails()});
    71                         } else {
    72                                 log.info("Unsupported schema: {0}", record.getRecordSchema());
     68                        for (SRUDiagnostic d : response.getDiagnostics()) {
     69                                diagnostics.add(new Diagnostic(d.getURI(), d.getMessage(), d.getDetails()));
    7370                        }
    7471                }
    7572        }
    7673
    77         void setDiagnostics(SRUSearchRetrieveResponse response) {
    78                 for (SRUDiagnostic d : response.getDiagnostics()) {
    79                         SRUSearchRetrieveRequest srurequest = response.getRequest();
    80                         diagnostics.add(new Diagnostic(d.getURI(), d.getMessage(), d.getDetails()));
     74        void addRecord(SRURecord record) {
     75                endpointReturnedRecords.getAndIncrement();
     76                if (record.isRecordSchema(ClarinFCSRecordData.RECORD_SCHEMA)) {
     77                        ClarinFCSRecordData rd = (ClarinFCSRecordData) record.getRecordData();
     78                        Resource resource = rd.getResource();
     79                        setClarinRecord(resource);
     80                        log.debug("Resource ref={0}, pid={1}, dataViews={2}",
     81                                        new Object[]{resource.getRef(), resource.getPid(), resource.hasDataViews()});
     82                } else if (record.isRecordSchema(SRUSurrogateRecordData.RECORD_SCHEMA)) {
     83                        SRUSurrogateRecordData r = (SRUSurrogateRecordData) record.getRecordData();
     84                        log.info("Surrogate diagnostic: uri={0}, message={1}, detail={2}",
     85                                        new Object[]{r.getURI(), r.getMessage(), r.getDetails()});
     86                } else {
     87                        log.info("Unsupported schema: {0}", record.getRecordSchema());
    8188                }
    8289        }
     
    130137
    131138        public JsonException getException() {
    132                 return exception;
     139                return exception.get();
    133140        }
    134141
    135         public int getStartRecord() {
    136                 return request.getStartRecord();
     142        public void setException(Exception xc) {
     143                exception.set(new JsonException(xc));
    137144        }
    138145
    139         public int getEndRecord() {
    140                 return request.getEndRecord();
     146        public int getEndpointReturnedRecords() {
     147                return endpointReturnedRecords.get();
    141148        }
    142149
    143150        public Corpus getCorpus() {
    144                 return request.getCorpus();
    145         }
    146 
    147         public String getSearchString() {
    148                 return request.getSearchString();
     151                return corpus;
    149152        }
    150153}
  • SRUAggregator/trunk/src/main/java/eu/clarin/sru/fcs/aggregator/search/Search.java

    r6081 r6092  
    3333        private static final AtomicLong counter = new AtomicLong(Math.abs(new Random().nextInt()));
    3434
     35        private final ThrottledClient searchClient;
     36        private final SRUVersion version;
    3537        private final Long id;
    3638        private final String query;
    3739        private final long createdAt = System.currentTimeMillis();
    3840        private final String searchLanguage;
    39         private final List<Request> requests = Collections.synchronizedList(new ArrayList<Request>());
    4041        private final List<Result> results = Collections.synchronizedList(new ArrayList<Result>());
    4142        private final Statistics statistics;
     
    4344        public Search(ThrottledClient searchClient, SRUVersion version,
    4445                        Statistics statistics, List<Corpus> corpora, String searchString,
    45                         String searchLanguage, int startRecord, int maxRecords
     46                        String searchLanguage, int maxRecords
    4647        ) {
     48                this.searchClient = searchClient;
     49                this.version = version;
    4750                this.id = counter.getAndIncrement();
    4851                this.query = searchString;
     
    5053                this.statistics = statistics;
    5154                for (Corpus corpus : corpora) {
    52                         executeSearch(searchClient, version, corpus, searchString, startRecord, maxRecords);
     55                        Result result = new Result(corpus);
     56                        executeSearch(result, query, 1, maxRecords);
     57                        results.add(result);
    5358                }
    5459        }
    5560
    56         private Request executeSearch(ThrottledClient searchClient, SRUVersion version,
    57                         final Corpus corpus, String searchString,
     61        public boolean searchForNextResults(String corpusId, int maxRecords) {
     62                for (Result r : results) {
     63                        if (r.getCorpus().getId().equals(corpusId)) {
     64                                executeSearch(r, query, r.getEndpointReturnedRecords() + 1, maxRecords);
     65                                return true;
     66                        }
     67                }
     68                return false;
     69        }
     70
     71        private void executeSearch(final Result result, String searchString,
    5872                        int startRecord, int maxRecords) {
    59                 final Request request = new Request(corpus, searchString, startRecord, startRecord + maxRecords - 1);
    60                 log.info("Executing search in '{}' query='{}' maxRecords='{}'", corpus, searchString, maxRecords);
     73                final Corpus corpus = result.getCorpus();
     74                log.info("Executing search in '{}' query='{}' maxRecords='{}'",
     75                                corpus, searchString, maxRecords);
    6176
    6277                SRUSearchRetrieveRequest searchRequest = new SRUSearchRetrieveRequest(corpus.getEndpoint().getUrl());
    6378                searchRequest.setVersion(version);
     79                searchRequest.setStartRecord(startRecord);
    6480                searchRequest.setMaximumRecords(maxRecords);
    6581                boolean legacy = corpus.getEndpoint().getProtocol().equals(FCSProtocolVersion.LEGACY);
     
    6884                                : ClarinFCSRecordData.RECORD_SCHEMA);
    6985                searchRequest.setQuery(searchString);
    70                 searchRequest.setStartRecord(startRecord);
    7186                if (corpus.getHandle() != null) {
    7287                        searchRequest.setExtraRequestData(legacy
     
    7590                                        corpus.getHandle());
    7691                }
    77                 requests.add(request);
     92                result.setInProgress(true);
    7893
    7994                try {
     
    8398                                        try {
    8499                                                statistics.addEndpointDatapoint(corpus.getInstitution(), corpus.getEndpoint(), stats.getQueueTime(), stats.getExecutionTime());
    85                                                 Result result = new Result(request, response, null);
    86                                                 results.add(result);
    87                                                 requests.remove(request);
     100                                                result.addResponse(response);
    88101                                                List<Diagnostic> diagnostics = result.getDiagnostics();
    89102                                                if (diagnostics != null && !diagnostics.isEmpty()) {
     
    96109                                        } catch (Throwable xc) {
    97110                                                log.error("search.onSuccess exception:", xc);
     111                                        } finally {
     112                                                result.setInProgress(false);
    98113                                        }
    99114                                }
     
    104119                                                statistics.addEndpointDatapoint(corpus.getInstitution(), corpus.getEndpoint(), stats.getQueueTime(), stats.getExecutionTime());
    105120                                                statistics.addErrorDatapoint(corpus.getInstitution(), corpus.getEndpoint(), xc, srureq.getRequestedURI().toString());
    106                                                 results.add(new Result(request, null, xc));
    107                                                 requests.remove(request);
     121                                                result.setException(xc);
    108122                                                log.error("search.onError: ", xc);
    109123                                        } catch (Throwable xxc) {
    110124                                                log.error("search.onError exception:", xxc);
     125                                        } finally {
     126                                                result.setInProgress(false);
    111127                                        }
    112128                                }
     
    115131                        log.error("SearchRetrieve error for " + corpus.getEndpoint().getUrl(), xc);
    116132                }
    117                 return request;
    118133        }
    119134
    120135        public Long getId() {
    121136                return id;
    122         }
    123 
    124         public List<Request> getRequests() {
    125                 List<Request> copy = new ArrayList<>();
    126                 synchronized (requests) {
    127                         copy.addAll(requests);
    128                 }
    129                 return copy;
    130137        }
    131138
  • SRUAggregator/trunk/src/main/resources/assets/base.css

    r6081 r6092  
    513513}
    514514
     515.statistics .alert-warning.legacy {
     516        color: #000000;
     517        background-color: #FFFFFF;
     518        border-color: #A00000;
     519}
  • SRUAggregator/trunk/src/main/resources/assets/js/main.js

    r6081 r6092  
    33"use strict";
    44
    5 var VERSION = "VERSION 2.0.0.α26";
     5var VERSION = "VERSION 2.0.0-beta-27";
    66var URLROOT = "/Aggregator-testing";
    77
     
    137137                                React.createElement("div", {className: "container"},
    138138                                        React.createElement("div", {className: "beta-tag"},
    139                                                 React.createElement("span", null, "ALPHA")
     139                                                React.createElement("span", null, "BETA")
    140140                                        )
    141141                                ),
     
    227227
    228228        renderDiagnostic: function(d) {
     229                var classes = "inline alert alert-warning " + (d.diagnostic.uri === 'LEGACY' ? "legacy" : "");
    229230                return  React.createElement("div", {key: d.diagnostic.uri},
    230                                         React.createElement("div", {className: "inline alert alert-warning"},
     231                                        React.createElement("div", {className: classes},
    231232                                                React.createElement("div", null,
    232233                                                         d.counter <= 1 ? false :
  • SRUAggregator/trunk/src/main/resources/assets/js/main.jsx

    r6081 r6092  
    33"use strict";
    44
    5 var VERSION = "VERSION 2.0.0.α26";
     5var VERSION = "VERSION 2.0.0-beta-27";
    66var URLROOT = "/Aggregator-testing";
    77
     
    137137                                <div className="container">
    138138                                        <div className="beta-tag">
    139                                                 <span>ALPHA</span>
     139                                                <span>BETA</span>
    140140                                        </div>
    141141                                </div>
     
    227227
    228228        renderDiagnostic: function(d) {
     229                var classes = "inline alert alert-warning " + (d.diagnostic.uri === 'LEGACY' ? "legacy" : "");
    229230                return  <div key={d.diagnostic.uri}>
    230                                         <div className="inline alert alert-warning">
     231                                        <div className={classes} >
    231232                                                <div>
    232233                                                        { d.counter <= 1 ? false :
  • SRUAggregator/trunk/src/main/resources/assets/js/search.js

    r6081 r6092  
    22(function() {
    33"use strict";
     4
     5var NO_MORE_RECORDS_DIAGNOSTIC_URI = "info:srw/diagnostic/1/61";
    46
    57window.MyAggregator = window.MyAggregator || {};
     
    6567                corpus.priority = 1; // used for ordering search results in corpus view
    6668                corpus.index = index; // original order, used for stable sort
    67         });
    68         this.recurse(function(corpus, index) {
    69                 if (corpus.institution.link > 1) {
    70                         corpus.selected = false;
    71                 }
    7269        });
    7370}
     
    180177
    181178        nohits: {
    182                 requests: [],
    183                 results: [],
     179                results: null,
    184180        },
    185181        anyLanguage: [multipleLanguageCode, "Any Language"],
     
    249245                });
    250246        },
     247        nextResults: function(corpusId) {
     248                // console.log("searching next results in corpus:", corpusId);
     249                this.props.ajax({
     250                        url: 'rest/search/'+this.state.searchId,
     251                        type: "POST",
     252                        data: {
     253                                corpusId: corpusId,
     254                                numberOfResults: this.state.numberOfResults,
     255                        },
     256                        success: function(searchId, textStatus, jqXHR) {
     257                                // console.log("search ["+query+"] ok: ", searchId, jqXHR);
     258                                var timeout = 250;
     259                                setTimeout(this.refreshSearchResults, timeout);
     260                                this.setState({ searchId: searchId, timeout: timeout });
     261                        }.bind(this),
     262                });
     263        },
    251264
    252265        refreshSearchResults: function() {
     
    258271                        success: function(json, textStatus, jqXHR) {
    259272                                var timeout = this.state.timeout;
    260                                 if (json.requests.length > 0) {
     273                                if (json.inProgress) {
    261274                                        if (timeout < 10000) {
    262275                                                timeout = 1.5 * timeout;
     
    267280                                        console.log("search ended; hits:", json);
    268281                                }
    269                                 this.setState({ hits: json, timeout: timeout });
     282                                var corpusHit = this.state.zoomedCorpusHit;
     283                                if (corpusHit) {
     284                                        for (var resi = 0; resi < json.results.length; resi++) {
     285                                                var res = json.results[resi];
     286                                                if (res.corpus.id === corpusHit.corpus.id) {
     287                                                        corpusHit = res;
     288                                                        break;
     289                                                }
     290                                        }
     291                                }
     292                                this.setState({ hits: json, timeout: timeout, zoomedCorpusHit: corpusHit});
    270293                        }.bind(this),
    271294                });
     
    293316                this.state.corpora.setVisibility(this.state.searchLayerId,
    294317                        languageFilter === 'byGuess' ? multipleLanguageCode : languageObj[0]);
    295                 this.setState({language: languageObj, languageFilter: languageFilter});
    296                 this.state.corpora.update();
     318                this.setState({
     319                        language: languageObj,
     320                        languageFilter: languageFilter,
     321                        corpora: this.state.corpora, // === this.state.corpora.update();
     322                });
    297323        },
    298324
    299325        setLayer: function(layerId) {
    300326                this.state.corpora.setVisibility(layerId, this.state.language[0]);
    301                 this.state.corpora.update();
    302                 this.setState({searchLayerId: layerId});
     327                this.setState({
     328                        searchLayerId: layerId,
     329                        hits: this.nohits,
     330                        searchId: null,
     331                        corpora: this.state.corpora, // === this.state.corpora.update();
     332                });
    303333        },
    304334
     
    319349                var noLangFiltering = this.state.languageFilter === 'byMeta';
    320350                var langCode = this.state.language[0];
    321                 return this.state.hits.results.map(function(corpusHit) {
    322                         return {
    323                                 corpus: corpusHit.corpus,
    324                                 startRecord: corpusHit.startRecord,
    325                                 endRecord: corpusHit.endRecord,
    326                                 exception: corpusHit.exception,
    327                                 diagnostics: corpusHit.diagnostics,
    328                                 searchString: corpusHit.searchString,
    329                                 kwics: noLangFiltering ? corpusHit.kwics :
    330                                         corpusHit.kwics.filter(function(kwic) {
    331                                                 return kwic.language === langCode || langCode === multipleLanguageCode || langCode === null;
    332                                         }),
    333                         };
    334                 });
     351                var results = null, inProgress = 0, hits = 0;
     352                if (this.state.hits.results) {
     353                        results = this.state.hits.results.map(function(corpusHit) {
     354                                return {
     355                                        corpus: corpusHit.corpus,
     356                                        inProgress: corpusHit.inProgress,
     357                                        exception: corpusHit.exception,
     358                                        diagnostics: corpusHit.diagnostics,
     359                                        kwics: noLangFiltering ? corpusHit.kwics :
     360                                                corpusHit.kwics.filter(function(kwic) {
     361                                                        return kwic.language === langCode ||
     362                                                               langCode === multipleLanguageCode ||
     363                                                               langCode === null;
     364                                                }),
     365                                };
     366                        });
     367                        for (var i = 0; i < results.length; i++) {
     368                                var result = results[i];
     369                                if (result.inProgress) {
     370                                        inProgress++;
     371                                }
     372                                if (result.kwics.length > 0) {
     373                                        hits ++;
     374                                }
     375                        }
     376                }
     377                return {
     378                        results: results,
     379                        hits: hits,
     380                        inProgress: inProgress,
     381                };
    335382        },
    336383
     
    354401        },
    355402
    356         handleChange: function(event) {
     403        onQuery: function(event) {
    357404                this.setState({query: event.target.value});
    358405        },
     
    377424        },
    378425
    379         renderAggregator: function() {
     426        render: function() {
    380427                var layer = layerMap[this.state.searchLayerId];
    381428                return  (
     
    390437                                                        React.createElement("input", {className: "form-control input-lg search", name: "query", type: "text",
    391438                                                                value: this.state.query, placeholder: this.props.placeholder,
    392                                                                 tabIndex: "1", onChange: this.handleChange, onKeyDown: this.handleKey}),
     439                                                                tabIndex: "1", onChange: this.onQuery, onKeyDown: this.handleKey}),
    393440                                                        React.createElement("div", {className: "input-group-btn"},
    394441                                                                React.createElement("button", {className: "btn btn-default input-lg", type: "button", onClick: this.search},
     
    467514                                React.createElement(Modal, {ref: "resultModal", title: this.renderZoomedResultTitle(this.state.zoomedCorpusHit)},
    468515                                        React.createElement(ZoomedResult, {corpusHit: this.state.zoomedCorpusHit,
     516                                                                  nextResults: this.nextResults,
    469517                                                                  getDownloadLink: this.getDownloadLink,
    470518                                                                  getToWeblichtLink: this.getToWeblichtLink,
     
    475523
    476524                                React.createElement("div", {className: "top-gap"},
    477                                         React.createElement(Results, {requests: this.state.hits.requests,
    478                                                          results: this.filterResults(),
     525                                        React.createElement(Results, {collhits: this.filterResults(),
    479526                                                         toggleResultModal: this.toggleResultModal,
    480527                                                         getDownloadLink: this.getDownloadLink,
     
    485532                        );
    486533        },
    487         render: function() {
    488                 return this.renderAggregator();
    489         }
    490534});
    491535
     
    622666        },
    623667
    624         renderDiagnostic: function(d) {
    625                 return  React.createElement("div", {className: "alert alert-warning", key: d.uri},
     668        renderDiagnostic: function(d, key) {
     669                if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) {
     670                        return false;
     671                }
     672                return  React.createElement("div", {className: "alert alert-warning", key: key},
    626673                                        React.createElement("div", null, "Diagnostic: ", d.message)
    627674                                );
     
    632679                        return false;
    633680                }
    634 
    635681                return corpusHit.diagnostics.map(this.renderDiagnostic);
    636682        },
     
    714760                                        React.createElement("li", null,
    715761                                                error ?
    716                                                         React.createElement("div", {className: "alert alert-danger", style: {margin:10}}, error) :
     762                                                        React.createElement("div", {className: "alert alert-danger", style: {margin:10, width:200}}, error) :
    717763                                                        React.createElement("a", {href: this.props.getToWeblichtLink(corpusId), target: "_blank"}, " ",
    718764                                                                "Send to Weblicht")
     
    729775        propTypes: {
    730776                corpusHit: PT.object,
     777                nextResults: PT.func.isRequired,
    731778                languageMap: PT.object.isRequired,
    732779                weblichtLanguages: PT.array.isRequired,
     
    737784        mixins: [ResultMixin],
    738785
     786        getInitialState: function() {
     787                return {
     788                        inProgress: false,
     789                };
     790        },
     791
     792        componentWillReceiveProps: function() {
     793                this.setState({inProgress: false});
     794        },
     795
     796        nextResults: function(e) {
     797                this.setState({inProgress: true});
     798                this.props.nextResults(this.props.corpusHit.corpus.id);
     799        },
     800
    739801        renderLanguages: function(languages) {
    740802                return languages
     
    742804                                .sort()
    743805                                .join(", ");
     806        },
     807
     808        renderMoreResults:function(){
     809                if (this.state.inProgress || this.props.corpusHit.inProgress)
     810                        return React.createElement("span", {style: {fontStyle:'italic'}}, "Retrieving results, please wait...");
     811
     812                var moreResults = true;
     813                for (var i = 0; i < this.props.corpusHit.diagnostics.length; i++) {
     814                        var d = this.props.corpusHit.diagnostics[i];
     815                        if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) {
     816                                moreResults = false;
     817                                break;
     818                        }
     819                }
     820                if (!moreResults)
     821                        return React.createElement("span", {style: {fontStyle:'italic'}}, "No other results available for this query");
     822                return  React.createElement("button", {className: "btn btn-default", onClick: this.nextResults},
     823                                        React.createElement("span", {className: "glyphicon glyphicon-option-horizontal", 'aria-hidden': "true"}), " More Results"
     824                                );
    744825        },
    745826
     
    777858
    778859                                                React.createElement("div", {style: {textAlign:'center', marginTop:10}},
    779                                                         React.createElement("button", {className: "btn btn-default"},
    780                                                                 React.createElement("span", {className: "glyphicon glyphicon-option-horizontal", 'aria-hidden': "true"}), " More Results"
    781                                                         )
     860                                                         this.renderMoreResults()
    782861                                                )
    783862
     
    789868var Results = React.createClass({displayName: 'Results',
    790869        propTypes: {
    791                 requests: PT.array.isRequired,
    792                 results: PT.array.isRequired,
     870                collhits: PT.object.isRequired,
    793871                searchedLanguage: PT.array.isRequired,
    794872                toggleResultModal: PT.func.isRequired,
     
    825903        },
    826904
    827         renderProgressMessage: function(hits) {
    828                 var total = this.props.requests.length + this.props.results.length;
    829                 var msg = hits + " matching collections found in " + this.props.results.length + " searched collections";
    830                 var percents = Math.round(100 * this.props.results.length / total);
     905        renderProgressMessage: function() {
     906                var collhits = this.props.collhits;
     907                var done = collhits.results.length - collhits.inProgress;
     908                var msg = collhits.hits + " matching collections found in " + done + " searched collections";
     909                var percents = Math.round(100 * collhits.hits / collhits.results.length);
    831910                var styleperc = {width: percents+"%"};
    832911                return  React.createElement("div", {style: {marginTop:10}},
    833912                                        React.createElement("div", null, msg),
    834                                         this.props.requests.length > 0 ?
     913                                        collhits.inProgress > 0 ?
    835914                                                React.createElement("div", {className: "progress", style: {marginBottom:10}},
    836915                                                        React.createElement("div", {className: "progress-bar progress-bar-striped active", role: "progressbar",
     
    847926
    848927        render: function() {
    849                 if (this.props.results.length + this.props.requests.length === 0) {
     928                var collhits = this.props.collhits;
     929                if (!collhits.results) {
    850930                        return false;
    851931                }
    852                 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length;
    853                 var showprogress = this.props.requests.length > 0;
     932                var showprogress = collhits.inProgress > 0;
    854933                return  React.createElement("div", null,
    855934                                        React.createElement(ReactCSSTransitionGroup, {transitionName: "fade"},
    856                                                  showprogress ? this.renderProgressMessage(hits) : React.createElement("div", {style: {height:20}}),
     935                                                 showprogress ? this.renderProgressMessage() : React.createElement("div", {style: {height:20}}),
    857936                                                React.createElement("div", {style: {marginBottom:2}},
    858937                                                         showprogress ? false :
    859                                                                 React.createElement("div", {className: "float-left"}, " ", hits + " matching collections found", " "),
     938                                                                React.createElement("div", {className: "float-left"}, " ", collhits.hits + " matching collections found", " "),
    860939                                                       
    861                                                          hits === 0 ? false :
     940                                                         collhits.hits === 0 ? false :
    862941                                                                React.createElement("div", {className: "float-right"},
    863942                                                                        React.createElement("div", null,
    864943                                                                                 this.renderDisplayKWIC(),
    865                                                                                  this.props.requests.length === 0 ?
     944                                                                                 collhits.inProgress === 0 ?
    866945                                                                                        React.createElement("div", {className: "inline"}, " ", this.renderDownloadLinks(), " ")
    867946                                                                                        :false
     
    872951                                                        React.createElement("div", {style: {clear:'both'}})
    873952                                                ),
    874                                                 this.props.results.map(this.renderResultPanel)
     953                                                collhits.results.map(this.renderResultPanel)
    875954                                        )
    876955                                );
  • SRUAggregator/trunk/src/main/resources/assets/js/search.jsx

    r6081 r6092  
    22(function() {
    33"use strict";
     4
     5var NO_MORE_RECORDS_DIAGNOSTIC_URI = "info:srw/diagnostic/1/61";
    46
    57window.MyAggregator = window.MyAggregator || {};
     
    6567                corpus.priority = 1; // used for ordering search results in corpus view
    6668                corpus.index = index; // original order, used for stable sort
    67         });
    68         this.recurse(function(corpus, index) {
    69                 if (corpus.institution.link > 1) {
    70                         corpus.selected = false;
    71                 }
    7269        });
    7370}
     
    180177
    181178        nohits: {
    182                 requests: [],
    183                 results: [],
     179                results: null,
    184180        },
    185181        anyLanguage: [multipleLanguageCode, "Any Language"],
     
    249245                });
    250246        },
     247        nextResults: function(corpusId) {
     248                // console.log("searching next results in corpus:", corpusId);
     249                this.props.ajax({
     250                        url: 'rest/search/'+this.state.searchId,
     251                        type: "POST",
     252                        data: {
     253                                corpusId: corpusId,
     254                                numberOfResults: this.state.numberOfResults,
     255                        },
     256                        success: function(searchId, textStatus, jqXHR) {
     257                                // console.log("search ["+query+"] ok: ", searchId, jqXHR);
     258                                var timeout = 250;
     259                                setTimeout(this.refreshSearchResults, timeout);
     260                                this.setState({ searchId: searchId, timeout: timeout });
     261                        }.bind(this),
     262                });
     263        },
    251264
    252265        refreshSearchResults: function() {
     
    258271                        success: function(json, textStatus, jqXHR) {
    259272                                var timeout = this.state.timeout;
    260                                 if (json.requests.length > 0) {
     273                                if (json.inProgress) {
    261274                                        if (timeout < 10000) {
    262275                                                timeout = 1.5 * timeout;
     
    267280                                        console.log("search ended; hits:", json);
    268281                                }
    269                                 this.setState({ hits: json, timeout: timeout });
     282                                var corpusHit = this.state.zoomedCorpusHit;
     283                                if (corpusHit) {
     284                                        for (var resi = 0; resi < json.results.length; resi++) {
     285                                                var res = json.results[resi];
     286                                                if (res.corpus.id === corpusHit.corpus.id) {
     287                                                        corpusHit = res;
     288                                                        break;
     289                                                }
     290                                        }
     291                                }
     292                                this.setState({ hits: json, timeout: timeout, zoomedCorpusHit: corpusHit});
    270293                        }.bind(this),
    271294                });
     
    293316                this.state.corpora.setVisibility(this.state.searchLayerId,
    294317                        languageFilter === 'byGuess' ? multipleLanguageCode : languageObj[0]);
    295                 this.setState({language: languageObj, languageFilter: languageFilter});
    296                 this.state.corpora.update();
     318                this.setState({
     319                        language: languageObj,
     320                        languageFilter: languageFilter,
     321                        corpora: this.state.corpora, // === this.state.corpora.update();
     322                });
    297323        },
    298324
    299325        setLayer: function(layerId) {
    300326                this.state.corpora.setVisibility(layerId, this.state.language[0]);
    301                 this.state.corpora.update();
    302                 this.setState({searchLayerId: layerId});
     327                this.setState({
     328                        searchLayerId: layerId,
     329                        hits: this.nohits,
     330                        searchId: null,
     331                        corpora: this.state.corpora, // === this.state.corpora.update();
     332                });
    303333        },
    304334
     
    319349                var noLangFiltering = this.state.languageFilter === 'byMeta';
    320350                var langCode = this.state.language[0];
    321                 return this.state.hits.results.map(function(corpusHit) {
    322                         return {
    323                                 corpus: corpusHit.corpus,
    324                                 startRecord: corpusHit.startRecord,
    325                                 endRecord: corpusHit.endRecord,
    326                                 exception: corpusHit.exception,
    327                                 diagnostics: corpusHit.diagnostics,
    328                                 searchString: corpusHit.searchString,
    329                                 kwics: noLangFiltering ? corpusHit.kwics :
    330                                         corpusHit.kwics.filter(function(kwic) {
    331                                                 return kwic.language === langCode || langCode === multipleLanguageCode || langCode === null;
    332                                         }),
    333                         };
    334                 });
     351                var results = null, inProgress = 0, hits = 0;
     352                if (this.state.hits.results) {
     353                        results = this.state.hits.results.map(function(corpusHit) {
     354                                return {
     355                                        corpus: corpusHit.corpus,
     356                                        inProgress: corpusHit.inProgress,
     357                                        exception: corpusHit.exception,
     358                                        diagnostics: corpusHit.diagnostics,
     359                                        kwics: noLangFiltering ? corpusHit.kwics :
     360                                                corpusHit.kwics.filter(function(kwic) {
     361                                                        return kwic.language === langCode ||
     362                                                               langCode === multipleLanguageCode ||
     363                                                               langCode === null;
     364                                                }),
     365                                };
     366                        });
     367                        for (var i = 0; i < results.length; i++) {
     368                                var result = results[i];
     369                                if (result.inProgress) {
     370                                        inProgress++;
     371                                }
     372                                if (result.kwics.length > 0) {
     373                                        hits ++;
     374                                }
     375                        }
     376                }
     377                return {
     378                        results: results,
     379                        hits: hits,
     380                        inProgress: inProgress,
     381                };
    335382        },
    336383
     
    354401        },
    355402
    356         handleChange: function(event) {
     403        onQuery: function(event) {
    357404                this.setState({query: event.target.value});
    358405        },
     
    377424        },
    378425
    379         renderAggregator: function() {
     426        render: function() {
    380427                var layer = layerMap[this.state.searchLayerId];
    381428                return  (
     
    390437                                                        <input className="form-control input-lg search" name="query" type="text"
    391438                                                                value={this.state.query} placeholder={this.props.placeholder}
    392                                                                 tabIndex="1" onChange={this.handleChange} onKeyDown={this.handleKey} />
     439                                                                tabIndex="1" onChange={this.onQuery} onKeyDown={this.handleKey} />
    393440                                                        <div className="input-group-btn">
    394441                                                                <button className="btn btn-default input-lg" type="button" onClick={this.search}>
     
    467514                                <Modal ref="resultModal" title={this.renderZoomedResultTitle(this.state.zoomedCorpusHit)}>
    468515                                        <ZoomedResult corpusHit={this.state.zoomedCorpusHit}
     516                                                                  nextResults={this.nextResults}
    469517                                                                  getDownloadLink={this.getDownloadLink}
    470518                                                                  getToWeblichtLink={this.getToWeblichtLink}
     
    475523
    476524                                <div className="top-gap">
    477                                         <Results requests={this.state.hits.requests}
    478                                                          results={this.filterResults()}
     525                                        <Results collhits={this.filterResults()}
    479526                                                         toggleResultModal={this.toggleResultModal}
    480527                                                         getDownloadLink={this.getDownloadLink}
     
    485532                        );
    486533        },
    487         render: function() {
    488                 return this.renderAggregator();
    489         }
    490534});
    491535
     
    622666        },
    623667
    624         renderDiagnostic: function(d) {
    625                 return  <div className="alert alert-warning" key={d.uri}>
     668        renderDiagnostic: function(d, key) {
     669                if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) {
     670                        return false;
     671                }
     672                return  <div className="alert alert-warning" key={key}>
    626673                                        <div>Diagnostic: {d.message}</div>
    627674                                </div>;
     
    632679                        return false;
    633680                }
    634 
    635681                return corpusHit.diagnostics.map(this.renderDiagnostic);
    636682        },
     
    714760                                        <li>
    715761                                                {error ?
    716                                                         <div className="alert alert-danger" style={{margin:10}}>{error}</div> :
     762                                                        <div className="alert alert-danger" style={{margin:10, width:200}}>{error}</div> :
    717763                                                        <a href={this.props.getToWeblichtLink(corpusId)} target="_blank">{" "}
    718764                                                                Send to Weblicht</a>
     
    729775        propTypes: {
    730776                corpusHit: PT.object,
     777                nextResults: PT.func.isRequired,
    731778                languageMap: PT.object.isRequired,
    732779                weblichtLanguages: PT.array.isRequired,
     
    737784        mixins: [ResultMixin],
    738785
     786        getInitialState: function() {
     787                return {
     788                        inProgress: false,
     789                };
     790        },
     791
     792        componentWillReceiveProps: function() {
     793                this.setState({inProgress: false});
     794        },
     795
     796        nextResults: function(e) {
     797                this.setState({inProgress: true});
     798                this.props.nextResults(this.props.corpusHit.corpus.id);
     799        },
     800
    739801        renderLanguages: function(languages) {
    740802                return languages
     
    742804                                .sort()
    743805                                .join(", ");
     806        },
     807
     808        renderMoreResults:function(){
     809                if (this.state.inProgress || this.props.corpusHit.inProgress)
     810                        return <span style={{fontStyle:'italic'}}>Retrieving results, please wait...</span>;
     811
     812                var moreResults = true;
     813                for (var i = 0; i < this.props.corpusHit.diagnostics.length; i++) {
     814                        var d = this.props.corpusHit.diagnostics[i];
     815                        if (d.uri === NO_MORE_RECORDS_DIAGNOSTIC_URI) {
     816                                moreResults = false;
     817                                break;
     818                        }
     819                }
     820                if (!moreResults)
     821                        return <span style={{fontStyle:'italic'}}>No other results available for this query</span>;
     822                return  <button className="btn btn-default" onClick={this.nextResults}>
     823                                        <span className="glyphicon glyphicon-option-horizontal" aria-hidden="true"/> More Results
     824                                </button>;
    744825        },
    745826
     
    777858
    778859                                                <div style={{textAlign:'center', marginTop:10}}>
    779                                                         <button className="btn btn-default">
    780                                                                 <span className="glyphicon glyphicon-option-horizontal" aria-hidden="true"/> More Results
    781                                                         </button>
     860                                                        { this.renderMoreResults() }
    782861                                                </div>
    783862
     
    789868var Results = React.createClass({
    790869        propTypes: {
    791                 requests: PT.array.isRequired,
    792                 results: PT.array.isRequired,
     870                collhits: PT.object.isRequired,
    793871                searchedLanguage: PT.array.isRequired,
    794872                toggleResultModal: PT.func.isRequired,
     
    825903        },
    826904
    827         renderProgressMessage: function(hits) {
    828                 var total = this.props.requests.length + this.props.results.length;
    829                 var msg = hits + " matching collections found in " + this.props.results.length + " searched collections";
    830                 var percents = Math.round(100 * this.props.results.length / total);
     905        renderProgressMessage: function() {
     906                var collhits = this.props.collhits;
     907                var done = collhits.results.length - collhits.inProgress;
     908                var msg = collhits.hits + " matching collections found in " + done + " searched collections";
     909                var percents = Math.round(100 * collhits.hits / collhits.results.length);
    831910                var styleperc = {width: percents+"%"};
    832911                return  <div style={{marginTop:10}}>
    833912                                        <div>{msg}</div>
    834                                         {this.props.requests.length > 0 ?
     913                                        {collhits.inProgress > 0 ?
    835914                                                <div className="progress" style={{marginBottom:10}}>
    836915                                                        <div className="progress-bar progress-bar-striped active" role="progressbar"
     
    847926
    848927        render: function() {
    849                 if (this.props.results.length + this.props.requests.length === 0) {
     928                var collhits = this.props.collhits;
     929                if (!collhits.results) {
    850930                        return false;
    851931                }
    852                 var hits = this.props.results.filter(function(corpusHit) { return corpusHit.kwics.length > 0; }).length;
    853                 var showprogress = this.props.requests.length > 0;
     932                var showprogress = collhits.inProgress > 0;
    854933                return  <div>
    855934                                        <ReactCSSTransitionGroup transitionName="fade">
    856                                                 { showprogress ? this.renderProgressMessage(hits) : <div style={{height:20}} />}
     935                                                { showprogress ? this.renderProgressMessage() : <div style={{height:20}} />}
    857936                                                <div style={{marginBottom:2}}>
    858937                                                        { showprogress ? false :
    859                                                                 <div className="float-left"> {hits + " matching collections found"} </div>
     938                                                                <div className="float-left"> {collhits.hits + " matching collections found"} </div>
    860939                                                        }
    861                                                         { hits === 0 ? false :
     940                                                        { collhits.hits === 0 ? false :
    862941                                                                <div className="float-right">
    863942                                                                        <div>
    864943                                                                                { this.renderDisplayKWIC() }
    865                                                                                 { this.props.requests.length === 0 ?
     944                                                                                { collhits.inProgress === 0 ?
    866945                                                                                        <div className="inline"> {this.renderDownloadLinks()} </div>
    867946                                                                                        :false
     
    872951                                                        <div style={{clear:'both'}}/>
    873952                                                </div>
    874                                                 {this.props.results.map(this.renderResultPanel)}
     953                                                {collhits.results.map(this.renderResultPanel)}
    875954                                        </ReactCSSTransitionGroup>
    876955                                </div>;
Note: See TracChangeset for help on using the changeset viewer.