Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support non-numeric values for Usage chart filter parameters #716

Merged
merged 14 commits into from
Dec 10, 2018

Conversation

ryanrath
Copy link
Contributor

@ryanrath ryanrath commented Nov 1, 2018

Description

Added the ability for Usage chart requests to provide non-numeric values for
'_filter' parameters. The new feature will attempt to translate these
non-numeric values into appropriate id values that can be used during chart
generation. Specifically this new feature is to support integration w/
Coldfront.

Motivation and Context

Tests performed

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project as found in the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@ryanrath ryanrath requested a review from smgallo November 1, 2018 14:05
@ryanrath ryanrath added this to the 8.1.0 milestone Nov 1, 2018
@ryanrath ryanrath added the enhancement Enhancement of the functionality of an existing feature label Nov 1, 2018
@plessbd plessbd changed the base branch from xdmod8.0 to xdmod8.1 November 9, 2018 16:55
Copy link
Member

@jpwhite4 jpwhite4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you need to create an integration test to confirm that the change works properly.

@@ -76,7 +76,7 @@ added with this command for CentOS 7:
# yum install httpd php php-cli php-mysql php-gd php-mcrypt \
gmp-devel php-gmp php-pdo php-xml \
php-pear-MDB2 php-pear-MDB2-Driver-mysql \
java-1.7.0-openjdk java-1.7.0-openjdk-devel \
java-1.8.0-openjdk java-1.8.0-openjdk-devel \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this documentation change belongs in this pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been reverted.

* @return int[]|array all current queries return an int array. If no rows are found then
* an empty array is returned.
*/
$lookupValue = function ($query, array $values) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the PHP PDO already has a function that will return the values of one column in an array.

$lookupvalue = function ($query, array $values) {
    $db = DB::factory('database');
    $stmt = $db->prepare($query);
    $stmt->execute($values);

    return implode(',', $stmt->fetchAll(PDO::FETCH_COLUMN, 0) );
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very cool, thanks! This will be included in the next commit.

{

/**
* Anonymous function to prevent code duplication. $query is expected to minimally fulfill
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I note that this anonymous function does not prevent code duplication since it only ever needs to be called once per call to translateFilterValues. In fact it results in more lines of code than simply having the code inline at the bottom of the switch statement and having the switch statement set the value of the $query.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... best intentions and all... Thanks for the catch. The code will be updated and actually simplified in the next set of commits.

};

if (!is_numeric($usageFilterValue)) {
$usageFilterValues = explode(',', $usageFilterValue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The $usageFilterValue variable already went through an explode() on line 976. Therefore it cannot have any more commas to be exploded again and the bulk of this code is unnecessary. Please can you double check this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double checked, you are correct, this value doesn't need to be exploded again. Here is the function body w/ updates so far ( Just finishing up writing integration tests before committing ) :

        // If the value is already a number then just return it. We do this because this function is
        // about tranlsating a non-numeric value to a numeric one ( i.e. an id )
        if (is_numeric($usageFilterValue)) {
            return $usageFilterValue;
        }

        $query = null;

        switch ($usageFilterType) {
            case 'pi':
                // NOTE: At the time of writing there is no instance where multiple records have
                // the same username but different person_id values.
                $query = "SELECT DISTINCT sa.person_id AS value FROM modw.systemaccount sa WHERE sa.username = :value;";
                break;
            case 'resource':
                $query = "SELECT id AS value FROM modw.resourcefact WHERE code = :value";
                break;
            case 'project':
                $query = "SELECT account_id AS value FROM modw_cloud.account WHERE display = :value";
                break;
        }

        if ($query !== null) {
            $db = DB::factory('database');

            $stmt = $db->prepare($query);
            $stmt->execute(array(':value' => $usageFilterValue));

            return implode(',', $stmt->fetchAll(PDO::FETCH_COLUMN, 0));
        }

        return $usageFilterValue;

$stmt = $db->prepare($query);
$stmt->execute(array(':value' => $usageFilterValue));

return implode(',', $stmt->fetchAll(PDO::FETCH_COLUMN, 0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you want to implode all results here. The rest of the code will only work if there is a single return value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per discussion earlier with @jpwhite4 and @ryanrath an upcoming commit will return an array of zero or more elements that can be iterated over to properly add the filters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update is in the latest version of the PR

@jpwhite4
Copy link
Member

What is the expected behavior of the code if a string value is supplied that does not match in the database. I.e if I ask for filtering by a PI that does not exist.

@ryanrath
Copy link
Contributor Author

ryanrath commented Dec 3, 2018

@smgallo Thoughts? What currently occurs is that that particular filter / value combo is ignored. So, resource_filter=frearson,robertson&pi_filter=superunknownpi is the same as resource_filter=frearson,robertson

@@ -1156,14 +1155,38 @@ private function translateFilterValue($usageFilterType, $usageFilterValue)
}

if ($query !== null) {
$filterValueIsString = preg_match('/[\'"]+/', $usageFilterValue) === 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regex will match quotes anywhere in the string not just those surrounding it. It should be anchored at the start of the string and shouldn't have the + (e.g., /^[\'"]/).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should also be anchored at the end probably /^[\'"].*[\'"]$/

$filterValueIsString = preg_match('/[\'"]+/', $usageFilterValue) === 1;

$value = $filterValueIsString
? preg_replace('/[\'"]+/', '', $usageFilterValue)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regex will trim any quote character from the string, which may not be what we want if we allow passing of a person's name in the future as it will remove apostrophes from anywhere in the string. We would be better served using trim($usageFilterValue, '\'"').

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, didn't know that PHP's trim allowed a char list to be trimmed. I'll update that.

if ($rows !== false) {
$rowCount = count($rows);

if ($rowCount === 0 && !$filterValueIsString) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are now forcing the caller to specify whether the value is an integer id or a string that needs lookup in a dimension table, we can get rid of this logic. If the value is not enclosed in quotes we can return immediately.

}

// If a string was provided but no id(s) were found then exception out.
throw new Exception("Invalid filter value detected");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets include the invalid filter in the error message:

Suggested change
throw new Exception("Invalid filter value detected");
throw new Exception(sprintf("Invalid filter value detected: %s", $usageFilterValue));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

facepalm Ignore the most recent commit, added the type instead of the value.

@ryanrath
Copy link
Contributor Author

ryanrath commented Dec 4, 2018

Alrighty, @jpwhite4 @smgallo I think I've got things up to date w/ the comments on here / what we've talked about ( and updated w/ xdmod8.1 ). If you could take another look at things and let me know if there's any other changes you'd like to see before merging that would rock.

smgallo
smgallo previously approved these changes Dec 5, 2018
ryanrath and others added 14 commits December 10, 2018 09:52
Added the ability for Usage chart requests to provide non-numeric values for
'_filter' parameters. The new feature will attempt to translate these
non-numeric values into appropriate id values that can be used during chart
generation. Specifically this new feature is to support integration w/
Coldfront.
Per code review comments I've simplified this function.
  - Moved the `is_numeric` check to the top of the function and used it as a
    short circuit.
  - Removed the anonymous function and moved the code it contained to the bottom
    of the function, executed contingent on which `case`, if any, is hit.
This new test checks that the `_filter` variables accept non-id
values (optionally, multiple comma delimited values). The test data includes the
following:
  - Tests run as all users ( pub, cd, cs, pi, usr )
  - Multiple Realms ( Jobs, Cloud )
  - For the Jobs realm, all combinations of: ( including single, multiple,
    numeric & non-numeric values )
    - 'resource', 'resource_filter'
    - 'pi', 'pi_filter'
  - For the Cloud realm: ( including single, multiple, numeric, and non-numeric
    values )
    - 'project', 'project_filter'
As this function should only ever return one value the code has been updated so
that this is the case.
- Updated `translateFilterValue` to return an array of values and the calling code
to work with an array as opposed to a single value. This is to support the use
case of a system account being associated with more than one person.

- Updated `translateFilterValue` to only interpret `$usageFilterValue` as a
  string if it contains quotes ( single or double ). This allows us to identify
  when the calling code is passing us a value that is intended to be
  interpreted as a string not a number.

- Updated Test Cases so that string values are quoted with both single / double
  quotes.

- Added additional test cases that test what happens when a string value is
  provided that is not found in the db.
Refactored a portion of the `convertChartRequest` function and combined the two
`for-each` loops that were populating the `$meFilters` variable. Now we have one
loop that has the same functionality.

Also a tiny comment update in the `translateFilterValue` function for the
'pi' filter type. Now that we always return an array the previous comment didn't
really make sense anymore.
Now that we have a reliable test for what constitutes a "string" ( i.e. must be
quoted ) we can simplify the internals of `translateFilterValue` by removing the
vast majority of the if/elseif block. Also added a few comments to help provide
context.
Added `$usageFilterType` to the message for the exception thrown in
`translateFilterValue`.
Added the invalid `$usageFilterValue` to the exception message and updated the
section in the tests that handled detecting when exceptions are thrown to handle
this update.
Co-Authored-By: ryanrath <ryanrath@buffalo.edu>
@ryanrath ryanrath merged commit 6df963f into ubccr:xdmod8.1 Dec 10, 2018
@smgallo smgallo changed the title Add new functionality to Usage chart requests Support non-numeric values for Usage chart filter parameters Apr 10, 2019
@smgallo smgallo added the Category:Metric Explorer Metric Explorer / Usage label Apr 10, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category:Metric Explorer Metric Explorer / Usage enhancement Enhancement of the functionality of an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants