def test_query_parsed_edge_cases(self): self.assertEqual( generate_query_parsed('foo', u'AND "def'), { 'bool': { 'must': [ {'text_phrase': {'foo': u'def'}} ] } } ) self.assertEqual( generate_query_parsed('foo', u'"def" AND'), { 'bool': { 'must': [ {'text_phrase': {'foo': u'def'}} ] } } ) self.assertEqual( generate_query_parsed('foo', u'foo\\bar'), {'text': {'foo': u'foobar'}} ) self.assertEqual( generate_query_parsed('foo', u'foo\\\\bar'), {'text': {'foo': u'foo\\bar'}} )
def dashboard(request): template = 'analytics/dashboard.html' output_format = request.GET.get('format', None) page = smart_int(request.GET.get('page', 1), 1) # Note: If we add additional querystring fields, we need to add # them to generate_dashboard_url. search_happy = request.GET.get('happy', None) search_platform = request.GET.get('platform', None) search_locale = request.GET.get('locale', None) search_product = request.GET.get('product', None) search_version = request.GET.get('version', None) search_query = request.GET.get('q', None) search_date_start = smart_date( request.GET.get('date_start', None), fallback=None) search_date_end = smart_date( request.GET.get('date_end', None), fallback=None) search_bigram = request.GET.get('bigram', None) selected = request.GET.get('selected', None) filter_data = [] current_search = {'page': page} search = ResponseMappingType.search() f = F() # If search happy is '0' or '1', set it to False or True, respectively. search_happy = {'0': False, '1': True}.get(search_happy, None) if search_happy in [False, True]: f &= F(happy=search_happy) current_search['happy'] = int(search_happy) def unknown_to_empty(text): """Convert "Unknown" to "" to support old links""" return u'' if text.lower() == u'unknown' else text if search_platform is not None: f &= F(platform=unknown_to_empty(search_platform)) current_search['platform'] = search_platform if search_locale is not None: f &= F(locale=unknown_to_empty(search_locale)) current_search['locale'] = search_locale if search_product is not None: f &= F(product=unknown_to_empty(search_product)) current_search['product'] = search_product if search_version is not None: # Note: We only filter on version if we're filtering on # product. f &= F(version=unknown_to_empty(search_version)) current_search['version'] = search_version if search_date_start is None and search_date_end is None: selected = '7d' if search_date_end is None: search_date_end = datetime.now() if search_date_start is None: search_date_start = search_date_end - timedelta(days=7) current_search['date_end'] = search_date_end.strftime('%Y-%m-%d') # Add one day, so that the search range includes the entire day. end = search_date_end + timedelta(days=1) # Note 'less than', not 'less than or equal', because of the added # day above. f &= F(created__lt=end) current_search['date_start'] = search_date_start.strftime('%Y-%m-%d') f &= F(created__gte=search_date_start) if search_query: current_search['q'] = search_query es_query = generate_query_parsed('description', search_query) search = search.query_raw(es_query) if search_bigram is not None: f &= F(description_bigrams=search_bigram) filter_data.append({ 'display': _('Bigram'), 'name': 'bigram', 'options': [{ 'count': 'all', 'name': search_bigram, 'display': search_bigram, 'value': search_bigram, 'checked': True }] }) search = search.filter(f).order_by('-created') # If the user asked for a feed, give him/her a feed! if output_format == 'atom': return generate_atom_feed(request, search) elif output_format == 'json': return generate_json_feed(request, search) # Search results and pagination if page < 1: page = 1 page_count = 20 start = page_count * (page - 1) end = start + page_count search_count = search.count() opinion_page = search[start:end] # Navigation facet data facets = search.facet( 'happy', 'platform', 'locale', 'product', 'version', filtered=bool(search._process_filters(f.filters))) # This loop does two things. First it maps 'T' -> True and 'F' -> # False. This is probably something EU should be doing for # us. Second, it restructures the data into a more convenient # form. counts = { 'happy': {}, 'platform': {}, 'locale': {}, 'product': {}, 'version': {} } for param, terms in facets.facet_counts().items(): for term in terms: name = term['term'] if name == 'T': name = True elif name == 'F': name = False counts[param][name] = term['count'] def empty_to_unknown(text): return _('Unknown') if text == u'' else text filter_data.extend([ counts_to_options( counts['happy'].items(), name='happy', display=_('Sentiment'), display_map={True: _('Happy'), False: _('Sad')}, value_map={True: 1, False: 0}, checked=search_happy), counts_to_options( counts['product'].items(), name='product', display=_('Product'), display_map=empty_to_unknown, checked=search_product) ]) # Only show the version if we're showing a specific # product. if search_product: filter_data.append( counts_to_options( counts['version'].items(), name='version', display=_('Version'), display_map=empty_to_unknown, checked=search_version) ) filter_data.extend( [ counts_to_options( counts['platform'].items(), name='platform', display=_('Platform'), display_map=empty_to_unknown, checked=search_platform), counts_to_options( counts['locale'].items(), name='locale', display=_('Locale'), checked=search_locale, display_map=locale_name), ] ) # Histogram data happy_data = [] sad_data = [] happy_f = f & F(happy=True) sad_f = f & F(happy=False) histograms = search.facet_raw( happy={ 'date_histogram': {'interval': 'day', 'field': 'created'}, 'facet_filter': search._process_filters(happy_f.filters) }, sad={ 'date_histogram': {'interval': 'day', 'field': 'created'}, 'facet_filter': search._process_filters(sad_f.filters) }, ).facet_counts() # p['time'] is number of milliseconds since the epoch. Which is # convenient, because that is what the front end wants. happy_data = dict((p['time'], p['count']) for p in histograms['happy']) sad_data = dict((p['time'], p['count']) for p in histograms['sad']) zero_fill(search_date_start, search_date_end, [happy_data, sad_data]) histogram = [ {'label': _('Happy'), 'name': 'happy', 'data': sorted(happy_data.items())}, {'label': _('Sad'), 'name': 'sad', 'data': sorted(sad_data.items())}, ] return render(request, template, { 'opinions': opinion_page, 'opinion_count': search_count, 'filter_data': filter_data, 'histogram': histogram, 'page': page, 'prev_page': page - 1 if start > 0 else None, 'next_page': page + 1 if end < search_count else None, 'current_search': current_search, 'selected': selected, 'atom_url': generate_dashboard_url(request), })
def analytics_search(request): template = 'analytics/analyzer/search.html' output_format = request.GET.get('format', None) page = smart_int(request.GET.get('page', 1), 1) # Note: If we add additional querystring fields, we need to add # them to generate_dashboard_url. search_happy = request.GET.get('happy', None) search_has_email = request.GET.get('has_email', None) search_platform = request.GET.get('platform', None) search_locale = request.GET.get('locale', None) search_country = request.GET.get('country', None) search_product = request.GET.get('product', None) search_domain = request.GET.get('domain', None) search_version = request.GET.get('version', None) search_query = request.GET.get('q', None) search_date_start = smart_date( request.GET.get('date_start', None), fallback=None) search_date_end = smart_date( request.GET.get('date_end', None), fallback=None) search_bigram = request.GET.get('bigram', None) selected = request.GET.get('selected', None) filter_data = [] current_search = {'page': page} search = ResponseMappingType.search() f = F() # If search happy is '0' or '1', set it to False or True, respectively. search_happy = {'0': False, '1': True}.get(search_happy, None) if search_happy in [False, True]: f &= F(happy=search_happy) current_search['happy'] = int(search_happy) # If search has_email is '0' or '1', set it to False or True, # respectively. search_has_email = {'0': False, '1': True}.get(search_has_email, None) if search_has_email in [False, True]: f &= F(has_email=search_has_email) current_search['has_email'] = int(search_has_email) def unknown_to_empty(text): """Convert "Unknown" to "" to support old links""" return u'' if text.lower() == u'unknown' else text if search_platform is not None: f &= F(platform=unknown_to_empty(search_platform)) current_search['platform'] = search_platform if search_locale is not None: f &= F(locale=unknown_to_empty(search_locale)) current_search['locale'] = search_locale if search_product is not None: f &= F(product=unknown_to_empty(search_product)) current_search['product'] = search_product # Only show the version if there's a product. if search_version is not None: # Note: We only filter on version if we're filtering on # product. f &= F(version=unknown_to_empty(search_version)) current_search['version'] = search_version # Only show the country if the product is Firefox OS. if search_country is not None and search_product == 'Firefox OS': f &= F(country=unknown_to_empty(search_country)) current_search['country'] = search_country if search_domain is not None: f &= F(url_domain=unknown_to_empty(search_domain)) current_search['domain'] = search_domain if search_date_start is None and search_date_end is None: selected = '7d' if search_date_end is None: search_date_end = datetime.now() if search_date_start is None: search_date_start = search_date_end - timedelta(days=7) current_search['date_end'] = search_date_end.strftime('%Y-%m-%d') # Add one day, so that the search range includes the entire day. end = search_date_end + timedelta(days=1) # Note 'less than', not 'less than or equal', because of the added # day above. f &= F(created__lt=end) current_search['date_start'] = search_date_start.strftime('%Y-%m-%d') f &= F(created__gte=search_date_start) if search_query: current_search['q'] = search_query es_query = generate_query_parsed('description', search_query) search = search.query_raw(es_query) if search_bigram is not None: f &= F(description_bigrams=search_bigram) filter_data.append({ 'display': 'Bigram', 'name': 'bigram', 'options': [{ 'count': 'all', 'name': search_bigram, 'display': search_bigram, 'value': search_bigram, 'checked': True }] }) search = search.filter(f).order_by('-created') # If they're asking for a CSV export, then send them to the export # screen. if output_format == 'csv': return _analytics_search_export(request, search) # Search results and pagination if page < 1: page = 1 page_count = 50 start = page_count * (page - 1) end = start + page_count search_count = search.count() opinion_page_ids = [mem[0] for mem in search.values_list('id')[start:end]] # We convert what we get back from ES to what's in the db so we # can get all the information. opinion_page = Response.objects.filter(id__in=opinion_page_ids) # Navigation facet data facets = search.facet( 'happy', 'platform', 'locale', 'country', 'product', 'version', 'url_domain', 'has_email', filtered=bool(search._process_filters(f.filters))) # This loop does two things. First it maps 'T' -> True and 'F' -> # False. This is probably something EU should be doing for # us. Second, it restructures the data into a more convenient # form. counts = { 'happy': {}, 'has_email': {}, 'platform': {}, 'locale': {}, 'country': {}, 'product': {}, 'version': {}, 'url_domain': {}, } for param, terms in facets.facet_counts().items(): for term in terms: name = term['term'] if name == 'T': name = True elif name == 'F': name = False counts[param][name] = term['count'] def empty_to_unknown(text): return 'Unknown' if text == u'' else text filter_data.extend([ counts_to_options( counts['happy'].items(), name='happy', display='Sentiment', display_map={True: 'Happy', False: 'Sad'}, value_map={True: 1, False: 0}, checked=search_happy), counts_to_options( counts['has_email'].items(), name='has_email', display='Has email', display_map={True: 'Yes', False: 'No'}, value_map={True: 1, False: 0}, checked=search_has_email), counts_to_options( counts['product'].items(), name='product', display='Product', display_map=empty_to_unknown, checked=search_product) ]) # Only show the version if we're showing a specific # product. if search_product: filter_data.append( counts_to_options( counts['version'].items(), name='version', display='Version', display_map=empty_to_unknown, checked=search_version) ) # Only show the country if the product is Firefox OS. if search_product == 'Firefox OS': filter_data.append( counts_to_options( counts['country'].items(), name='country', display='Country', checked=search_country, display_map=country_name), ) filter_data.extend( [ counts_to_options( counts['platform'].items(), name='platform', display='Platform', display_map=empty_to_unknown, checked=search_platform), counts_to_options( counts['locale'].items(), name='locale', display='Locale', checked=search_locale, display_map=locale_name), counts_to_options( counts['url_domain'].items(), name='domain', display='Domain', checked=search_domain, display_map=empty_to_unknown), ] ) return render(request, template, { 'opinions': opinion_page, 'opinion_count': search_count, 'filter_data': filter_data, 'page': page, 'prev_page': page - 1 if start > 0 else None, 'next_page': page + 1 if end < search_count else None, 'current_search': current_search, 'selected': selected, })
def test_query_parsed(self): self.assertEqual( generate_query_parsed('foo', u'abc'), {'text': {'foo': u'abc'}}) self.assertEqual( generate_query_parsed('foo', u'abc def'), {'text': {'foo': u'abc def'}}, ) self.assertEqual( generate_query_parsed('foo', u'abc "def" ghi'), { 'bool': { 'minimum_should_match': 1, 'should': [ {'text': {'foo': u'abc'}}, {'text_phrase': {'foo': u'def'}}, {'text': {'foo': u'ghi'}}, ] } } ) self.assertEqual( generate_query_parsed('foo', u'abc AND "def"'), { 'bool': { 'must': [ {'text': {'foo': u'abc'}}, {'text_phrase': {'foo': u'def'}}, ] } } ) self.assertEqual( generate_query_parsed('foo', u'abc OR "def" AND ghi'), { 'bool': { 'minimum_should_match': 1, 'should': [ {'text': {'foo': u'abc'}}, {'bool': { 'must': [ {'text_phrase': {'foo': u'def'}}, {'text': {'foo': u'ghi'}}, ] }} ] } } ) self.assertEqual( generate_query_parsed('foo', u'abc AND "def" OR ghi'), { 'bool': { 'must': [ {'text': {'foo': u'abc'}}, {'bool': { 'minimum_should_match': 1, 'should': [ {'text_phrase': {'foo': u'def'}}, {'text': {'foo': u'ghi'}}, ] }} ] } } ) self.assertEqual( generate_query_parsed('foo', u'14.1\\" screen'), {'text': {'foo': u'14.1" screen'}} )