def _get_hash_for_parent_level(group: Group, id: int,
                               levels_overview: LevelsOverview) -> str:
    # If this is violated, there cannot be a 1:1 mapping between level and hash.
    assert 0 <= id < levels_overview.current_level

    # This cache never needs explicit invalidation because during every level
    # change, the group ID changes.
    #
    # No idea if the query is slow, caching just because I can.
    cache_key = f"group-parent-level-hash:{group.id}:{id}"

    return_hash: str = cache.get(cache_key)

    if return_hash is None:
        query = (Query("events", Entity("events")).set_select([
            Function("arrayElement", [Column("hierarchical_hashes"), id + 1],
                     "hash")
        ]).set_where(_get_group_filters(group)).set_limit(1))

        return_hash: str = get_path(snuba.raw_snql_query(query), "data", 0,
                                    "hash")  # type: ignore
        cache.set(cache_key, return_hash)

    assert return_hash
    return return_hash
Esempio n. 2
0
def get_levels_overview(group):
    query = (Query("events", Entity("events")).set_select([
        Column("primary_hash"),
        Function("max", [Function("length", [Column("hierarchical_hashes")])],
                 "num_levels"),
        _current_level_expr(group),
    ]).set_where(_get_group_filters(group)).set_groupby(
        [Column("primary_hash")]))

    res = snuba.raw_snql_query(
        query, referrer="api.group_hashes_levels.get_levels_overview")

    if not res["data"]:
        raise NoEvents()

    if len(res["data"]) > 1:
        raise MergedIssues()

    assert len(res["data"]) == 1

    fields = res["data"][0]

    if fields["num_levels"] <= 0:
        raise NotHierarchical()

    # TODO: Cache this if it takes too long. This is called from multiple
    # places, grouping overview and then again in the new-issues endpoint.

    return LevelsOverview(
        current_level=fields["current_level"] - 1,
        only_primary_hash=fields["primary_hash"],
        num_levels=fields["num_levels"],
    )
def _query_snuba(group: Group, id: int, offset=None, limit=None):
    query = (Query("events", Entity("events")).set_select([
        Function(
            "arrayElement",
            [
                Column("hierarchical_hashes"),
                Function("least", [
                    id + 1,
                    Function("length", [Column("hierarchical_hashes")])
                ]),
            ],
            "new_materialized_hash",
        ),
        Function("argMax",
                 [Column("event_id"), Column("timestamp")], "latest_event_id"),
        Function("max", [Column("timestamp")], "latest_event_timestamp"),
        Function("count", [], "event_count"),
    ]).set_groupby([Column("new_materialized_hash")]).set_orderby(
        [OrderBy(Column("latest_event_timestamp"), Direction.DESC)]))

    levels_overview = get_levels_overview(group)

    # These conditions are always valid
    common_where = [
        Condition(Column("primary_hash"), Op.EQ,
                  levels_overview.only_primary_hash),
        Condition(Column("project_id"), Op.EQ, group.project_id),
    ]

    if id >= levels_overview.current_level:
        # Good path: Since we increase the level we can easily constrain the
        # entire query by group_id and timerange
        query = query.set_where(common_where + _get_group_filters(group))
    else:
        # Bad path: We decreased the level and now we need to count events from
        # other groups. If we cannot filter by group_id, we can also not
        # restrict the timerange to anything at all. The Snuba API still
        # requires us to set a timerange, so we set it to the maximum of 90d.
        #
        # Luckily the minmax index on group_id alone is reasonably efficient so
        # that filtering by timerange (=primary key) is only a little bit
        # faster.
        now = datetime.datetime.now()
        new_materialized_hash = _get_hash_for_parent_level(
            group, id, levels_overview)
        query = query.set_where(common_where + [
            Condition(
                Function("arrayElement",
                         [Column("hierarchical_hashes"), id + 1]),
                Op.EQ,
                new_materialized_hash,
            ),
            Condition(Column("timestamp"), Op.GTE, now -
                      datetime.timedelta(days=90)),
            Condition(Column("timestamp"), Op.LT, now +
                      datetime.timedelta(seconds=10)),
        ])

    if offset is not None:
        query = query.set_offset(offset)

    if limit is not None:
        query = query.set_limit(limit)

    return snuba.raw_snql_query(
        query, referrer="api.group_hashes_levels.get_level_new_issues")["data"]