Example #1
0
class QueryHandler:
    """Handles report queries and responses."""
    def __init__(self, parameters):
        """Establish query handler.

        Args:
            parameters    (QueryParameters): parameter object for query

        """
        LOG.debug(f"Query Params: {parameters}")
        self.dh = DateHelper()
        parameters = self.filter_to_order_by(parameters)
        self.tenant = parameters.tenant
        self.access = parameters.access
        self.parameters = parameters
        self.default_ordering = self._mapper._report_type_map.get(
            "default_ordering")
        self.time_interval = []
        self._max_rank = 0

        self.time_scope_units = self.parameters.get_filter("time_scope_units")
        if self.parameters.get_filter("time_scope_value"):
            self.time_scope_value = int(
                self.parameters.get_filter("time_scope_value"))
        # self.time_order = parameters["date"]

        # self.start_datetime = parameters["start_date"]
        # self.end_datetime = parameters["end_date"]
        for param, attr in [("start_date", "start_datetime"),
                            ("end_date", "end_datetime")]:
            p = self.parameters.get(param)
            if p:
                setattr(
                    self, attr,
                    datetime.datetime.combine(parser.parse(p).date(),
                                              self.dh.midnight,
                                              tzinfo=UTC))
            else:
                setattr(self, attr, None)

        if self.resolution == "monthly":
            self.date_to_string = lambda dt: dt.strftime("%Y-%m")
            self.string_to_date = lambda dt: datetime.datetime.strptime(
                dt, "%Y-%m").date()
            self.date_trunc = TruncMonthString
            self.gen_time_interval = DateHelper().list_months
        else:
            self.date_to_string = lambda dt: dt.strftime("%Y-%m-%d")
            self.string_to_date = lambda dt: datetime.datetime.strptime(
                dt, "%Y-%m-%d").date()
            self.date_trunc = TruncDayString
            self.gen_time_interval = DateHelper().list_days

        if not (self.start_datetime or self.end_datetime):
            self._get_timeframe()

        self._create_time_interval()

    # FIXME: move this to a standalone utility function.
    @staticmethod
    def has_wildcard(in_list):
        """Check if list has wildcard.

        Args:
            in_list (List[String]): List of strings to check for wildcard
        Return:
            (Boolean): if wildcard is present in list

        """
        if isinstance(in_list, bool):
            return False
        if not in_list:
            return False
        return any(WILDCARD == item for item in in_list)

    @property
    def order(self):
        """Extract order_by parameter and apply ordering to the appropriate field.

        Returns:
            (String): Ordering value. Default is '-total'

        Example:
            `order_by[total]=asc` returns `total`
            `order_by[total]=desc` returns `-total`

        """
        order_map = {"asc": "", "desc": "-"}
        order = []
        order_by = self.parameters.get("order_by", self.default_ordering)

        for order_field, order_direction in order_by.items():
            if order_direction not in order_map and order_field == "date":
                # We've overloaded date to hold a specific date, not asc/desc
                order.append(order_direction)
            else:
                order.append(f"{order_map[order_direction]}{order_field}")

        return order

    @property
    def order_field(self):
        """Order-by field name.

        The default is 'total'
        """
        order_by = self.parameters.get("order_by", self.default_ordering)
        return list(order_by.keys()).pop()

    @property
    def order_direction(self):
        """Order-by orientation value.

        Returns:
            (str) 'asc' or 'desc'; default is 'desc'

        """
        order_by = self.parameters.get("order_by", self.default_ordering)
        return list(order_by.values()).pop()

    @property
    def max_rank(self):
        """Return the max rank of a ranked list."""
        return self._max_rank

    @max_rank.setter
    def max_rank(self, max_rank):
        """Max rank setter."""
        self._max_rank = max_rank

    @property
    def resolution(self):
        """Extract resolution or provide default.

        Returns:
            (String): The value of how data will be sliced.

        """
        return self.parameters.get_filter("resolution", default="daily")

    def check_query_params(self, key, in_key):
        """Test if query parameters has a given key and key within it.

        Args:
        key     (String): key to check in query parameters
        in_key  (String): key to check if key is found in query parameters

        Returns:
            (Boolean): True if they keys given appear in given query parameters.

        """
        return self.parameters and key in self.parameters and in_key in self.parameters.get(
            key)  # noqa: W504

    def get_time_scope_units(self):
        """Extract time scope units or provide default.

        Returns:
            (String): The value of how data will be sliced.

        """
        if self.time_scope_units:
            return self.time_scope_units

        time_scope_units = self.parameters.get_filter("time_scope_units",
                                                      default="day")
        self.time_scope_units = time_scope_units
        return self.time_scope_units

    def get_time_scope_value(self):
        """Extract time scope value or provide default.

        Returns:
            (Integer): time relative value providing query scope

        """
        if self.time_scope_value:
            return self.time_scope_value

        time_scope_value = self.parameters.get_filter("time_scope_value",
                                                      default=-10)
        self.time_scope_value = int(time_scope_value)
        return self.time_scope_value

    def _get_timeframe(self):
        """Obtain timeframe start and end dates.

        Returns:
            (DateTime): start datetime for query filter
            (DateTime): end datetime for query filter

        """
        time_scope_value = self.get_time_scope_value()
        time_scope_units = self.get_time_scope_units()
        start = None
        end = None
        if time_scope_units == "month":
            if time_scope_value == -1:
                # get current month
                start = self.dh.this_month_start
                end = self.dh.today
            elif time_scope_value == -3:
                start = self.dh.relative_month_start(-2)
                end = self.dh.month_end(start)
            else:
                # get previous month
                start = self.dh.last_month_start
                end = self.dh.last_month_end
        else:
            if time_scope_value == -10:
                # get last 10 days
                start = self.dh.n_days_ago(self.dh.this_hour, 9)
                end = self.dh.this_hour
            elif time_scope_value == -90:
                start = self.dh.n_days_ago(self.dh.this_hour, 89)
                end = self.dh.this_hour
            else:
                # get last 30 days
                start = self.dh.n_days_ago(self.dh.this_hour, 29)
                end = self.dh.this_hour

        self.start_datetime = start
        self.end_datetime = end
        return (self.start_datetime, self.end_datetime, self.time_interval)

    def _create_time_interval(self):
        """Create list of date times in interval.

        Returns:
            (List[DateTime]): List of all interval slices by resolution

        """
        self.time_interval = sorted(
            self.gen_time_interval(self.start_datetime, self.end_datetime))
        return self.time_interval

    def _get_date_delta(self):
        """Return a time delta."""
        if self.time_scope_value in [-1, -2, -3]:
            date_delta = relativedelta.relativedelta(
                months=abs(self.time_scope_value))
        elif self.time_scope_value in (-90, -30, -10):
            date_delta = datetime.timedelta(days=abs(self.time_scope_value))
        else:
            date_delta = datetime.timedelta(days=10)
        return date_delta

    def _get_time_based_filters(self, delta=False):
        if delta:
            date_delta = self._get_date_delta()
            start = self.start_datetime - date_delta
            end = self.end_datetime - date_delta
        else:
            start = self.start_datetime
            end = self.end_datetime

        start_filter = QueryFilter(field="usage_start",
                                   operation="gte",
                                   parameter=start.date())
        end_filter = QueryFilter(field="usage_start",
                                 operation="lte",
                                 parameter=end.date())
        return start_filter, end_filter

    def _get_filter(self, delta=False):
        """Create dictionary for filter parameters.

        Args:
            delta (Boolean): Construct timeframe for delta
        Returns:
            (Dict): query filter dictionary

        """
        filters = QueryFilterCollection()

        # add time constraint filters
        start_filter, end_filter = self._get_time_based_filters(delta)
        filters.add(query_filter=start_filter)
        filters.add(query_filter=end_filter)

        return filters

    def _get_gcp_filter(self, delta=False):
        """Create dictionary for filter parameters for GCP.

        For the gcp filters when the time scope is -1 or -2 we remove
        the usage_start & usage_end filters and only use the invoice month.

        Args:
            delta (Boolean): Construct timeframe for delta
        Returns:
            (Dict): query filter dictionary
        """
        filters = QueryFilterCollection()
        if delta:
            date_delta = self._get_date_delta()
            start = self.start_datetime - date_delta
            end = self.end_datetime - date_delta
        else:
            start = self.start_datetime
            end = self.end_datetime
        start_filter = QueryFilter(field="usage_start",
                                   operation="gte",
                                   parameter=start.date())
        end_filter = QueryFilter(field="usage_start",
                                 operation="lte",
                                 parameter=end.date())

        invoice_months = self.dh.gcp_find_invoice_months_in_date_range(
            start.date(), end.date())
        invoice_filter = QueryFilter(field="invoice_month",
                                     operation="in",
                                     parameter=invoice_months)
        filters.add(invoice_filter)
        if self.parameters.get_filter(
                "time_scope_value") and self.time_scope_value in [-1, -2]:
            # we don't add the time filters to time scopes -1 or -2 unless they are using delta.
            if delta:
                filters.add(query_filter=start_filter)
                filters.add(query_filter=end_filter)
        else:
            filters.add(query_filter=start_filter)
            filters.add(query_filter=end_filter)
        return filters

    def filter_to_order_by(self, parameters):  # noqa: C901
        """Remove group_by[NAME]=* and replace it with group_by[NAME]=X.

        The parameters object contains a list of filters and a list of group_bys.

        For example, if the parameters object contained the following:
        group_by[X] = Y
        group_by[Z] = *     # removes this line
        filter[Z] = L
        filter[X] = Y

        The returned parameters object would contain lists that look like this:

        group_by[X] = Y
        group_by[Z] = L     # adds this line
        filter[Z] = L
        filter[X] = Y

        Thereby removing the star when there is a filter provided.

        Args:
            parameters (QueryParameters): The parameters object

        Returns:
            parameters (QueryParameters): The parameters object

        """
        # find if there is a filter[key]=value that matches this group_by[key]=value
        for key, value in parameters.parameters.get("group_by", {}).items():
            if self.has_wildcard(value):
                filter_value = parameters.parameters.get("filter", {}).get(key)
                if filter_value:
                    parameters.parameters["group_by"][key] = filter_value
        return parameters

    def set_access_filters(self, access, filt, filters):
        """
        Sets the access filters to ensure RBAC restrictions given the users access,
        the current filter and the filter collection
        Args:
            access (list) the list containing the users relevant access
            filt (list or dict) contains the filters that need
            filters (QueryFilterCollection) the filter collection to add the new filters to
        returns:
            None
        """
        for _filt in filt if isinstance(filt, list) else [filt]:
            check_field_type = None
            try:
                if hasattr(self, "query_table"):
                    # Reports APIs
                    check_field_type = self.query_table._meta.get_field(
                        _filt.get("field", "")).get_internal_type()
                elif hasattr(self, "data_sources"):
                    # Tags APIs
                    check_field_type = (
                        self.data_sources[0].get("db_table")._meta.get_field(
                            _filt.get("field", "")).get_internal_type())
            except FieldDoesNotExist:
                pass

            _filt[
                "operation"] = "contains" if check_field_type == "ArrayField" else "in"
            q_filter = QueryFilter(parameter=access, **_filt)
            filters.add(q_filter)
Example #2
0
class DateHelperTest(TestCase):
    """Test the DateHelper."""

    def setUp(self):
        """Test setup."""
        self.date_helper = DateHelper()
        self.date_helper._now = datetime.datetime(1970, 1, 10, 12, 59, 59)

    def test_this_hour(self):
        """Test this_hour property."""
        expected = datetime.datetime(1970, 1, 10, 12, 0, 0, 0)
        self.assertEqual(self.date_helper.this_hour, expected)

    def test_next_hour(self):
        """Test next_hour property."""
        expected = datetime.datetime(1970, 1, 10, 13, 0, 0, 0)
        self.assertEqual(self.date_helper.next_hour, expected)

    def test_prev_hour(self):
        """Test previous_hour property."""
        expected = datetime.datetime(1970, 1, 10, 11, 0, 0, 0)
        self.assertEqual(self.date_helper.previous_hour, expected)

    def test_today(self):
        """Test today property."""
        expected = datetime.datetime(1970, 1, 10, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.today, expected)

    def test_yesterday(self):
        """Test yesterday property."""
        date_helper = DateHelper()
        date_helper._now = datetime.datetime(1970, 1, 1, 12, 59, 59)
        expected = datetime.datetime(1969, 12, 31, 0, 0, 0, 0)
        self.assertEqual(date_helper.yesterday, expected)

    def test_tomorrow(self):
        """Test tomorrow property."""
        expected = datetime.datetime(1970, 1, 11, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.tomorrow, expected)

    def test_this_month_start(self):
        """Test this_month_start property."""
        expected = datetime.datetime(1970, 1, 1, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.this_month_start, expected)

    def test_this_month_end(self):
        """Test this_month_end property."""
        expected = datetime.datetime(1970, 1, 31, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.this_month_end, expected)

    def test_next_month_start(self):
        """Test next_month_start property."""
        expected = datetime.datetime(1970, 2, 1, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.next_month_start, expected)

    def test_next_month_end(self):
        """Test next_month_end property."""
        expected = datetime.datetime(1970, 2, 28, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.next_month_end, expected)

    def test_last_month_start(self):
        """Test last_month_start property."""
        expected = datetime.datetime(1969, 12, 1, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.last_month_start, expected)

    def test_last_month_end(self):
        """Test last_month_end property."""
        expected = datetime.datetime(1969, 12, 31, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.last_month_end, expected)

    def test_relative_month_start_neg(self):
        expected = datetime.datetime(1969, 10, 1)
        self.assertEqual(self.date_helper.relative_month_start(-3), expected)

    def test_relative_month_start_pos(self):
        expected = datetime.datetime(1970, 6, 1)
        self.assertEqual(self.date_helper.relative_month_start(5), expected)

    def test_relative_month_end_neg(self):
        expected = datetime.datetime(1969, 9, 30)
        self.assertEqual(self.date_helper.relative_month_end(-4), expected)

    def test_relative_month_end_pos(self):
        expected = datetime.datetime(1970, 8, 31)
        self.assertEqual(self.date_helper.relative_month_end(7), expected)

    def test_next_month(self):
        """Test the next_month method."""
        current_month = datetime.datetime.now().replace(microsecond=0, second=0, minute=0, hour=0, day=1)
        last_month = current_month - relativedelta(months=1)
        self.assertEqual(current_month, DateHelper().next_month(last_month))

    def test_previous_month(self):
        """Test the previous_month method."""
        current_month = datetime.datetime.now().replace(microsecond=0, second=0, minute=0, hour=0, day=1)
        last_month = current_month - relativedelta(months=1)
        self.assertEqual(last_month, DateHelper().previous_month(current_month))

    def test_list_days(self):
        """Test the list_days method."""
        first = datetime.datetime.now().replace(microsecond=0, second=0, minute=0, hour=0, day=1)
        second = first.replace(day=2)
        third = first.replace(day=3)
        expected = [first, second, third]
        self.assertEqual(self.date_helper.list_days(first, third), expected)

    def test_list_months(self):
        """Test the list_months method."""
        first = datetime.datetime(1970, 1, 1)
        second = datetime.datetime(1970, 2, 1)
        third = datetime.datetime(1970, 3, 1)
        expected = [first, second, third]
        self.assertEqual(self.date_helper.list_months(first, third), expected)

    def test_n_days_ago(self):
        """Test the n_days_ago method."""
        delta_day = datetime.timedelta(days=1)
        today = timezone.now().replace(microsecond=0, second=0, minute=0, hour=0)
        two_days_ago = (today - delta_day) - delta_day
        self.assertEqual(self.date_helper.n_days_ago(today, 2), two_days_ago)

    def test_month_start(self):
        """Test month start method."""
        today = self.date_helper.today
        expected = datetime.datetime(1970, 1, 1, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.month_start(today), expected)

    def test_month_end(self):
        """Test month end method."""
        today = self.date_helper.today
        expected = datetime.datetime(1970, 1, 31, 0, 0, 0, 0)
        self.assertEqual(self.date_helper.month_end(today), expected)

        today_date = today.date()
        expected = datetime.date(1970, 1, 31)
        self.assertEqual(self.date_helper.month_end(today_date), expected)

    def test_midnight(self):
        """Test midnight property."""
        expected = datetime.time(0, 0, 0, 0)
        self.assertEqual(self.date_helper.midnight, expected)