From 9f531efb1f1b3def111323af1fb12747cbc0853b Mon Sep 17 00:00:00 2001 From: Daniel Wysocki <daniel.wysocki@ligo.org> Date: Wed, 24 Apr 2024 15:13:29 +0000 Subject: [PATCH] Splitting O4 into O4a/O4b, adding ER16 --- config/settings/base.py | 3 +- docs/user_docs/source/queries.rst | 93 ++++++++++++++++++++++++++++ gracedb/search/constants.py | 39 ++++++++++-- gracedb/search/query/events.py | 9 ++- gracedb/search/query/superevents.py | 8 +-- gracedb/search/tests/test_queries.py | 24 ++++--- gracedb/search/utils.py | 18 ++++++ gracedb/static/css/override-new.css | 18 ++++++ gracedb/superevents/views.py | 13 ++-- gracedb/templates/navbar_frag.html | 33 ++++++++-- 10 files changed, 226 insertions(+), 32 deletions(-) diff --git a/config/settings/base.py b/config/settings/base.py index b8539933e..6522bdae2 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -712,7 +712,8 @@ EVENT_SUPEREVENT_WINDOW_BEFORE = 100 EVENT_SUPEREVENT_WINDOW_AFTER = 100 # Define which observation periods to show on the public events page: -PUBLIC_PAGE_RUNS = ['O4', 'ER15', 'O3'] +# TODO: Group O4b and O4a under O4, once implemented. +PUBLIC_PAGE_RUNS = ['O4', 'O4b', 'O4a', 'ER16', 'ER15', 'O3'] # Define how long to cache the public page: PUBLIC_PAGE_CACHING = int(get_from_env('DJANGO_PUBLIC_PAGE_CACHING', diff --git a/docs/user_docs/source/queries.rst b/docs/user_docs/source/queries.rst index f48046157..03b503b9b 100644 --- a/docs/user_docs/source/queries.rst +++ b/docs/user_docs/source/queries.rst @@ -136,6 +136,95 @@ Examples: - ``is_preferred_event: True`` - ``is_preferred_event: False`` +By run identifier +----------------- +Events (and superevents) can be queried by Observation/Engineering/Science run identifier, +which is based on a preset ``gpstime`` range. The ``runid:`` keyword is optional. +Examples and available options are below: + +- ``runid: O4`` +- ``O3`` +- ``O1 O2`` + +.. list-table:: GraceDB Queryable Run ID's + :widths: 25 25 + :header-rows: 1 + + * - runid + - gpstime/t_0 range + + * - ``O4`` + - (1368975618, 1389456018), (1396796418, 1423238418) + + * - ``O4b`` + - (1396796418, 1423238418) + + * - ``O4a`` + - (1368975618, 1389456018) + + * - ``ER16`` + - (1394982018, 1396796418) + + * - ``ER15`` + - (1366556418, 1368975618) + + * - ``O3`` + - (1238166018, 1269363618) + + * - ``ER14`` + - (1235750418, 1238166018) + + * - ``ER13`` + - (1228838418, 1229176818) + + * - ``O2`` + - (1164556817, 1187733618) + + * - ``O1`` + - (1126623617, 1136649617) + + * - ``ER8`` + - (1123858817, 1126623617) + + * - ``ER7`` + - (1117400416, 1118329216) + + * - ``ER6`` + - (1102089616, 1102863616) + + * - ``ER5`` + - (1073822416, 1078876816) + + * - ``ER4`` + - (1057881616, 1061856016) + + * - ``ER3`` + - (1044136816, 1045785616) + + * - ``ER2`` + - (1026666016, 1028480416) + + * - ``ER1`` + - (1011601640, 1013299215) + + * - ``ER1test`` + - (1010944815, 1011601640) + + * - ``S6`` + - (931035296, 971622087) + + * - ``S6A`` + - (931035296, 935798487) + + * - ``S6B`` + - (937800015, 947260815) + + * - ``S6C`` + - (949449543, 961545687) + + * - ``S6D`` + - (956707143, 971622087) + Superevent queries ================== @@ -219,6 +308,10 @@ By label -------- Same as for events. +By run identifier +----------------- +Same as for events. + By public status ---------------- Use the ``is_public`` or ``is_exposed`` keywords. diff --git a/gracedb/search/constants.py b/gracedb/search/constants.py index 33c84a1e5..be9544835 100644 --- a/gracedb/search/constants.py +++ b/gracedb/search/constants.py @@ -17,11 +17,21 @@ ExpressionOperator.setParseAction(lambda toks: EXPR_OPERATORS[toks[0]]) # Dict of LIGO run names (keys) and GPS time range tuples (values) RUN_MAP = { - # O4 Start May 24, 2023...1500UTC? 18 months later...Nov. 24, 2024. - # FIXME: change the end date in a future release: - "O4": (1368975618, 1416495618), - # ER15 start Apr 26, 2023 1600 UTC - "ER15": (1366560018, 1368975618), + "O4": { + # https://observing.docs.ligo.org/plan/ + # The LIGO Hanford (LHO), LIGO Livingston (LLO), and Virgo detectors transitioned + # to the regular observing run O4b at 15:00 UTC on 10 April 2024. O4b will run + # until February 2025 (FIXME specific date TBD), with no further planned breaks + # in observing. + "O4b": (1396796418, 1423238418), + # O4a started May 24, 2023 1500UTC and ended Jan 16, 2024 1600UTC + "O4a": (1368975618, 1389456018), + }, + # ER16 started March 20, 2024 1500UTC, ended when O4b started + # https://dcc.ligo.org/DocDB/0191/M2300233/002/ER16_O4b_Start.pdf + "ER16": (1394982018, 1396796418), + # ER15 start Apr 26, 2023 1500 UTC + "ER15": (1366556418, 1368975618), # O3 suspended early due to COVID-19: # https://www.ligo.caltech.edu/news/ligo20200326 # 01 Apr 2019 15:00:00 UTC - 27 Mar 2020 16:00:00 UTC @@ -59,3 +69,22 @@ RUN_MAP = { "S6D" : (956707143, 971622087), } +# Flattens run map, creating a consistently typed dict mapping +# run/segment names to a list of (start, stop) gpstimes. +def flatten_run_map(dictionary): + flattened = {} + for key, value in dictionary.items(): + if isinstance(value, dict): + # Insert the full run + flattened[key] = list(value.values()) + # Insert the individual segments + for segment_name, segment_times in value.items(): + flattened[segment_name] = [segment_times] + else: + # Insert a full run that has no segments + flattened[key] = [value] + + return flattened + +# A flat RUN_MAP list to use in queries: +RUN_MAP_FLAT = flatten_run_map(RUN_MAP) diff --git a/gracedb/search/query/events.py b/gracedb/search/query/events.py index 94a244c53..623695cb9 100644 --- a/gracedb/search/query/events.py +++ b/gracedb/search/query/events.py @@ -30,8 +30,8 @@ nltime = nltime_.setParseAction(lambda toks: toks["calculatedTime"]) from events.models import Group, Pipeline, Search from .labels import getLabelQ from .superevents import parse_superevent_id, superevent_expr -from ..constants import RUN_MAP, ExpressionOperator -from ..utils import maybeRange +from ..constants import RUN_MAP, RUN_MAP_FLAT, ExpressionOperator +from ..utils import maybeRange, run_map_search_filter # hasfar flag @@ -46,11 +46,10 @@ gpsQ = Optional(Suppress(Keyword("gpstime:"))) + (gpstime^gpstimeRange) gpsQ = gpsQ.setParseAction(maybeRange("gpstime")) # run ids -runid = Or(list(map(CaselessLiteral, list(RUN_MAP)))).setName("run id") +runid = Or(list(map(CaselessLiteral, list(RUN_MAP_FLAT)))).setName("run id") #runidList = OneOrMore(runid).setName("run id list") runQ = (Optional(Suppress(Keyword("runid:"))) + runid) -runQ = runQ.setParseAction(lambda toks: ("gpstime", Q(gpstime__range= - RUN_MAP[toks[0]]))) +runQ = runQ.setParseAction(lambda toks: ("gpstime", run_map_search_filter(toks[0], 'gpstime'))) # Gracedb ID gid = Suppress(Word("gG", exact=1)) + Word("0123456789") diff --git a/gracedb/search/query/superevents.py b/gracedb/search/query/superevents.py index bd9ef3497..b7e29cbe2 100644 --- a/gracedb/search/query/superevents.py +++ b/gracedb/search/query/superevents.py @@ -16,8 +16,8 @@ from core.utils import letters_to_int, int_to_letters from events.nltime import nlTimeExpression as nltime_ from superevents.models import Superevent from .labels import getLabelQ -from ..constants import RUN_MAP, ExpressionOperator -from ..utils import maybeRange +from ..constants import RUN_MAP, RUN_MAP_FLAT, ExpressionOperator +from ..utils import maybeRange, run_map_search_filter from pyparsing import Word, nums, Literal, CaselessLiteral, delimitedList, \ Suppress, QuotedString, Keyword, Combine, Or, Optional, OneOrMore, \ @@ -96,10 +96,10 @@ parameter_dicts = { 'runid': { 'keyword': 'runid', 'keywordOptional': True, - 'value': Or(list(map(CaselessLiteral, list(RUN_MAP)))).setName( + 'value': Or(list(map(CaselessLiteral, RUN_MAP_FLAT))).setName( "run id"), 'doRange': False, - 'parseAction': lambda toks: ("t_0", Q(t_0__range=RUN_MAP[toks[0]])), + 'parseAction': lambda toks: ("t_0", run_map_search_filter(toks[0], 't_0')), }, # t_0: 123456.0987 OR gpstime: 123456.0987 't_0': { diff --git a/gracedb/search/tests/test_queries.py b/gracedb/search/tests/test_queries.py index a3087f9eb..31ea1ed1f 100644 --- a/gracedb/search/tests/test_queries.py +++ b/gracedb/search/tests/test_queries.py @@ -10,7 +10,8 @@ from django.conf import settings from django.db.models import Q from superevents.models import Superevent -from search.constants import RUN_MAP +from search.constants import RUN_MAP, RUN_MAP_FLAT +from search.utils import run_map_search_filter from search.query.events import parseQuery from search.query.superevents import parseSupereventQuery @@ -142,8 +143,8 @@ SUPEREVENT_QUERY_TEST_DATA = [ Q(events__id__range=[123, 129])), ] # Add tests based on run IDs -RUNID_QUERY_DATA = [(k, Q(t_0__range=v) & (DEFAULT_Q)) - for k,v in RUN_MAP.items()] +RUNID_QUERY_DATA = [(k, run_map_search_filter(k, 't_0') & (DEFAULT_Q)) + for k in RUN_MAP_FLAT] SUPEREVENT_QUERY_TEST_DATA.extend(RUNID_QUERY_DATA) @pytest.mark.parametrize("query,expected_Q_result", SUPEREVENT_QUERY_TEST_DATA) @@ -296,11 +297,20 @@ EVENT_QUERY_TEST_DATA = [ ("is_preferred_event: False", Q(superevent_preferred_for__isnull=True) & DEFAULT_EVENT_Q), # By run name - ("runid: O1", Q(gpstime__range=RUN_MAP["O1"]) & DEFAULT_EVENT_Q), - ("O1", Q(gpstime__range=RUN_MAP["O1"]) & DEFAULT_EVENT_Q), + ("runid: O1", run_map_search_filter("O1", "gpstime") & DEFAULT_EVENT_Q), + ("O1", run_map_search_filter("O1", "gpstime") & DEFAULT_EVENT_Q), ("O1 O2", - ( Q(gpstime__range=RUN_MAP["O1"]) - | Q(gpstime__range=RUN_MAP["O2"])) & DEFAULT_EVENT_Q), + ( run_map_search_filter("O1", "gpstime") + | run_map_search_filter("O2", "gpstime")) & DEFAULT_EVENT_Q), + # try out the O4 filters: + ("O4", run_map_search_filter("O4", "gpstime") & DEFAULT_EVENT_Q), + # Manually test O4: + ("O4", (Q(gpstime__range=RUN_MAP_FLAT["O4"][0]) | + Q(gpstime__range=RUN_MAP_FLAT["O4"][1])) & DEFAULT_EVENT_Q), + # test one of O4's subranges: + ("O4a", run_map_search_filter("O4a", "gpstime") & DEFAULT_EVENT_Q), + # and manually: + ("O4b", Q(gpstime__range=RUN_MAP_FLAT["O4b"][0]) & DEFAULT_EVENT_Q), # 'nevents' ("nevents: 5", Q(nevents="5") & DEFAULT_EVENT_Q), ] diff --git a/gracedb/search/utils.py b/gracedb/search/utils.py index 4b5c2b00b..ec24c442e 100644 --- a/gracedb/search/utils.py +++ b/gracedb/search/utils.py @@ -3,6 +3,7 @@ from pyparsing import Keyword, CaselessKeyword, oneOf, Literal, Or, \ OneOrMore, ZeroOrMore, Optional, Suppress from django.db.models import Q, QuerySet +from .constants import RUN_MAP_FLAT # Set up logger logger = logging.getLogger(__name__) @@ -74,3 +75,20 @@ def handle_binary_ops(toks, op="or"): new_toks = toks return new_toks, updated + + +# Given a string representation of an observing/engineering period (like 'O4'), +# and a time range parameter (t_0 for superevents, gpstime for events), then +# return a Q filter over that range, or period of ranges. +#-------------------------------------------------------------------------- +def run_map_search_filter(obsrun, param): + # start with a blank filter: + q_filt = Q() + + if obsrun not in RUN_MAP_FLAT: + raise ValueError('observation period not found in RUN_MAP_FLAT') + + for tup in RUN_MAP_FLAT[obsrun]: + q_filt |= Q(**{f'{param}__range': tup}) + + return q_filt diff --git a/gracedb/static/css/override-new.css b/gracedb/static/css/override-new.css index 17348dadc..492e34271 100644 --- a/gracedb/static/css/override-new.css +++ b/gracedb/static/css/override-new.css @@ -333,3 +333,21 @@ table.dataTable.no-footer { } /* end dataTable table css */ + +/* Add dropdown multilevel menu stuff */ +.dropdown-menu li { +position: relative; +} +.dropdown-menu .dropdown-submenu { +display: none; +position: absolute; +left: 100%; +top: -7px; +} +.dropdown-menu .dropdown-submenu-left { +right: 100%; +left: auto; +} +.dropdown-menu > li:hover > .dropdown-submenu { +display: block; +} diff --git a/gracedb/superevents/views.py b/gracedb/superevents/views.py index f380ea017..666f5ae0f 100644 --- a/gracedb/superevents/views.py +++ b/gracedb/superevents/views.py @@ -29,6 +29,7 @@ from .mixins import ExposeHideMixin, OperatorSignoffMixin, \ RRTViewMixin from .models import Superevent, VOEvent, Log from search.constants import RUN_MAP +from search.utils import run_map_search_filter from .utils import get_superevent_by_date_id_or_404, \ get_superevent_by_sid_or_gwid_or_404 @@ -284,10 +285,12 @@ class SupereventPublic(DisplayFarMixin, ListView): if self.obsrun not in settings.PUBLIC_PAGE_RUNS: raise Http404 + # The base query for exposed production superevents: qs = Superevent.objects.filter(is_exposed=True, - category=Superevent.SUPEREVENT_CATEGORY_PRODUCTION, - t_0__range=RUN_MAP[self.obsrun]) - return qs + category=Superevent.SUPEREVENT_CATEGORY_PRODUCTION) + + # return the events in the date range: + return qs.filter(run_map_search_filter(self.obsrun, 't_0')) # Define significance per run: @@ -296,7 +299,7 @@ class SupereventPublic(DisplayFarMixin, ListView): # https://git.ligo.org/computing/gracedb/server/-/issues/303#note_725082 # So use Q filters for these: significant_filter = Q() - if self.obsrun in ['ER15', 'O4']: + if self.obsrun in ['ER15', 'ER16', 'O4a', 'O4b', 'O4']: significant_filter = Q(labels__name='ADVREQ') | \ Q(labels__name='ADVOK') | \ Q(labels__name='ADVNO') @@ -314,7 +317,7 @@ class SupereventPublic(DisplayFarMixin, ListView): # Note: this value is also used as a trigger to show the significance # button and bullet. def insignificant_docs(self, run): - if run in ['ER15', 'O4']: + if run in ['ER15', 'ER16', 'O4a', 'O4b', 'O4']: return 'https://emfollow.docs.ligo.org/userguide/content.html#significance' else: return None diff --git a/gracedb/templates/navbar_frag.html b/gracedb/templates/navbar_frag.html index 1e0410f2a..57bc15d9d 100644 --- a/gracedb/templates/navbar_frag.html +++ b/gracedb/templates/navbar_frag.html @@ -1,5 +1,23 @@ {% load static %} +<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script> +<script> +$( document ).ready( function () { + $( '.navbar a.dropdown-toggle' ).on( 'click', function ( e ) { + var $el = $( this ); + var $parent = $( this ).offsetParent( ".dropdown-menu" ); + $( this ).parent( "li" ).toggleClass( 'show' ); + + if ( !$parent.parent().hasClass( 'navbar-nav' ) ) { + $el.next().css( { "top": $el[0].offsetTop, "left": $parent.outerWidth() - 4 } ); + } + $( '.navbar-nav li.show' ).not( $( this ).parents( "li" ) ).removeClass( "show" ); + return false; + } ); +} ); + +</script> + <div class="fixed-top" style="margin-top: -75px; padding-top: 75px; box-shadow: 0 0 0.2rem rgba(0,0,0,.1), 0 0.2rem 0.4rem rgba(0,0,0,.2);"> <nav class="navbar navbar-expand-md navbar-gracedb navbar-dark"> <a class="navbar-brand text-white" href="/"> @@ -23,11 +41,16 @@ <button type="button" class="btn btn-small shadow-none btn-split dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <span class="sr-only">Toggle Dropdown</span> </button> - <div class="dropdown-menu"> - <a class="dropdown-item" href="{% url "superevents:public-alerts" "O4" %}">O4</a> - <a class="dropdown-item" href="{% url "superevents:public-alerts" "ER15"%}">ER15</a> - <a class="dropdown-item" href="{% url "superevents:public-alerts" "O3" %}">O3</a> - </div> + <ul class="dropdown-menu"> + <li> <div class="dropdown-item dropdown-toggle"><a href="{% url "superevents:public-alerts" "O4" %}" style="color: black; text-decoration: none;">O4</a></div> + <ul class="dropdown-menu dropdown-submenu"> + <li> <a class="dropdown-item" href="{% url "superevents:public-alerts" "O4b" %}">O4b</a> </li> + <li> <a class="dropdown-item" href="{% url "superevents:public-alerts" "O4a" %}">O4a</a> </li> + </ul></li> + <li> <a class="dropdown-item" href="{% url "superevents:public-alerts" "ER16"%}">ER16</a> </li> + <li> <a class="dropdown-item" href="{% url "superevents:public-alerts" "ER15"%}">ER15</a> </li> + <li> <a class="dropdown-item" href="{% url "superevents:public-alerts" "O3" %}">O3</a> </li> + </ul> </div> </li> -- GitLab