From a21e9b56b58514e896542e0bb4e837965dd30f95 Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sat, 13 Aug 2016 15:09:11 -0400 Subject: [PATCH] working asana task loading + curl caching --- controller/action/ContentActions.class.php | 3 +- data/api/Asana.class.php | 102 +++++++++++++++-- data/api/Prefinery.class.php | 20 ++-- data/cache/Apc.class.php | 15 +++ lib/tools/Curl.class.php | 45 +++++++- view/template/content/roadmap.php | 20 ++++ web/scss/_blog.scss | 125 +++++++++++---------- 7 files changed, 241 insertions(+), 89 deletions(-) create mode 100644 data/cache/Apc.class.php diff --git a/controller/action/ContentActions.class.php b/controller/action/ContentActions.class.php index 694974ea..972d5705 100644 --- a/controller/action/ContentActions.class.php +++ b/controller/action/ContentActions.class.php @@ -113,9 +113,8 @@ class ContentActions extends Actions public static function executeRoadmap() { - print_r(Asana::listRoadmapTasks()); - die('wtf'); return ['content/roadmap', [ + 'tasks' => Asana::listRoadmapTasks() ]]; } diff --git a/data/api/Asana.class.php b/data/api/Asana.class.php index 1197e99c..481af8c8 100644 --- a/data/api/Asana.class.php +++ b/data/api/Asana.class.php @@ -2,24 +2,110 @@ class Asana { - protected static $curlOptions = []; + protected static $curlOptions = ['json_response' => true, 'cache' => true]; public static function listRoadmapTasks() { - return static::get('abc'); - $uri = '/projects/projectId-id/tasks'; + /* + * return static::get('/projects'); + [55] => Array + ( + [id] => 158602294500138 + [name] => Browser + ) + + [56] => Array + ( + [id] => 158602294500137 + [name] => Daemon + ) + + [57] => Array + ( + [id] => 161514803479899 + [name] => Blockchain and Wallet + ) + + + [60] => Array + ( + [id] => 158829550589337 + [name] => Reporting and Analytics + ) + + [61] => Array + ( + [id] => 158602294500214 + [name] => Other + ) + + [62] => Array + ( + [id] => 136290697597644 + [name] => CI + ) + + [63] => Array + ( + [id] => 158602294500249 + [name] => Documentation + ) + */ +// return static::get('/projects'); + $projects = [ + 158602294500138 => 'LBRY Browser', + 158602294500137 => 'LBRYnet', + 161514803479899 => 'Blockchain and Wallets', + 158829550589337 => 'Reporting and Analytics', + 136290697597644 => 'Integration and Building', + 158602294500249 => 'Documentation', + 158602294500214 => 'Other' + ]; + + $tasks = [ + 'ongoing' => [], + 'upcoming' => [] + ]; + + $categories = array_keys($tasks); + foreach($projects as $projectId => $projectName) + { + $projectTasks = static::get('/tasks?' . http_build_query(['completed_since' => 'now', 'project' => $projectId])); + $key = null; + foreach ($projectTasks as $task) + { + if (mb_substr($task['name'], -1) === ':') //if task ends with ":", it is a category heading so switch the active key + { + $key = array_reduce($categories, function($carry, $category) use($task) { + return $carry ?: (strcasecmp($task['name'], $category . ':') === 0 ? $category : null); + }); + } + elseif ($key && $task['name']) + { + $fullTask = static::get('/tasks/' . $task['id']); + $tasks[$key][] = array_intersect_key($fullTask, ['name' => null, 'due_on' => null]) + [ + 'project' => $projectName, + 'assignee' => $fullTask['assignee'] ? ucwords($fullTask['assignee']['name']) : '' + ]; + } + } + } + + return $tasks; } protected static function get($endpoint, array $data = []) { $apiKey = '0/c85cfce3591c2a3e214408cfba7cc44c'; - $options = []; // $apiKey = Config::get('prefinery_key'); // curl -H "Authorization: Bearer ACCESS_TOKEN" https://app.asana.com/api/1.0/users/me - $options['headers'] = ['Authorization: Bearer ' . $apiKey]; - return - Curl::get('https://app.asana.com/api/1.0/users/me', $data, $options) - ; + + $options = static::$curlOptions + [ + 'headers' => ['Authorization: Bearer ' . $apiKey] + ]; + + $responseData = Curl::get('https://app.asana.com/api/1.0' . $endpoint, $data, $options); + return isset($responseData['data']) ? $responseData['data'] : []; } } diff --git a/data/api/Prefinery.class.php b/data/api/Prefinery.class.php index f2c52713..46bf28c7 100644 --- a/data/api/Prefinery.class.php +++ b/data/api/Prefinery.class.php @@ -17,7 +17,8 @@ class Prefinery 'Accept: application/json', 'Content-type: application/json' ], - 'json_data' => true + 'json_data' => true, + 'json_response' => true ]; @@ -106,7 +107,7 @@ class Prefinery $apiKey = Config::get('prefinery_key'); $options = static::$curlOptions; $options['headers'][] = 'X-HTTP-Method-Override: PUT'; - return static::decodePrefineryResponse( + return static::processPrefineryResponse( Curl::put(static::DOMAIN . static::PREFIX . $endpoint . '.json?api_key=' . $apiKey, $data, $options) ); } @@ -114,7 +115,7 @@ class Prefinery protected static function get($endpoint, array $data = []) { $apiKey = Config::get('prefinery_key'); - return static::decodePrefineryResponse( + return static::processPrefineryResponse( Curl::get(static::DOMAIN . static::PREFIX . $endpoint . '.json?api_key=' . $apiKey, $data, static::$curlOptions) ); } @@ -122,22 +123,15 @@ class Prefinery protected static function post($endpoint, array $data = [], $allowEmptyResponse = true) { $apiKey = Config::get('prefinery_key'); - return static::decodePrefineryResponse( + return static::processPrefineryResponse( Curl::post(static::DOMAIN . static::PREFIX . $endpoint . '.json?api_key=' . $apiKey, $data, static::$curlOptions), $allowEmptyResponse ); } - protected static function decodePrefineryResponse($rawBody, $allowEmptyResponse = true) + protected static function processPrefineryResponse(array $data, $allowEmptyResponse = true) { - if (!$rawBody) - { - throw new PrefineryException('Empty cURL response.'); - } - - $data = json_decode($rawBody, true); - - if (!$allowEmptyResponse && !$data && $data !== []) + if (!$allowEmptyResponse && !$data) { throw new PrefineryException('Received empty or improperly encoded response.'); } diff --git a/data/cache/Apc.class.php b/data/cache/Apc.class.php new file mode 100644 index 00000000..af3fdc31 --- /dev/null +++ b/data/cache/Apc.class.php @@ -0,0 +1,15 @@ + false, 'headers' => [], 'verify' => true, 'timeout' => 5, @@ -36,6 +38,7 @@ class Curl 'password' => null, 'cookie' => null, 'json_data' => false, + 'json_response' => false ]; $invalid = array_diff_key($options, $defaults); @@ -44,13 +47,28 @@ class Curl throw new DomainException('Invalid curl options: ' . join(', ', array_keys($invalid))); } - $options = array_merge($defaults, $options); - if (!in_array($method, [static::GET, static::POST, static::PUT])) { throw new DomainException('Invalid method: ' . $method); } + $options = array_merge($defaults, $options); + + if (!Apc::isEnabled()) + { + $options['cache'] = false; + } + + if ($options['cache']) + { + $cacheKey = md5('z' . $url . $method . serialize($options) . serialize($params)); + $cachedData = apc_fetch($cacheKey); + if ($cachedData) + { + return $cachedData; + } + } + if ($options['headers'] && $options['headers'] !== array_values($options['headers'])) // associative array { throw new DomainException('Headers must not be an associative array. Its a simple array with values of the form "Header: value"'); @@ -95,7 +113,6 @@ class Curl if (in_array($method, [static::PUT, static::POST])) { - print_r($options['json_data'] ? json_encode($params) : http_build_query($params)); curl_setopt($ch, CURLOPT_POSTFIELDS, $options['json_data'] ? json_encode($params) : http_build_query($params)); } @@ -136,7 +153,16 @@ class Curl return strlen($h); }); - $responseContent = curl_exec($ch); + $rawResponse = curl_exec($ch); + + if ($options['json_response']) + { + $responseContent = $rawResponse ? json_decode($rawResponse, true) : []; + } + else + { + $responseContent = $rawResponse; + } $statusCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); @@ -147,7 +173,14 @@ class Curl curl_close($ch); - return [$statusCode, $headers, $responseContent]; + $response = [$statusCode, $headers, $responseContent]; + + if ($options['cache']) + { + apc_store($cacheKey, $response, is_numeric($options['cache']) ? $options['cache'] : static::DEFAULT_CACHE); + } + + return $response; } } diff --git a/view/template/content/roadmap.php b/view/template/content/roadmap.php index 73744c44..df15aaf8 100644 --- a/view/template/content/roadmap.php +++ b/view/template/content/roadmap.php @@ -5,5 +5,25 @@

{{roadmap.title}}

+
+ The LBRY roadmap pulls in information dynamically from our internal project management system and public + GitHub page. +
+

Scheduled Changes

+ $categoryTasks): ?> +
+

+ + + + + + + + + +
+
+
\ No newline at end of file diff --git a/web/scss/_blog.scss b/web/scss/_blog.scss index e57bd408..943f0702 100644 --- a/web/scss/_blog.scss +++ b/web/scss/_blog.scss @@ -105,74 +105,79 @@ { margin-top: $spacing-vertical; } - table +} +.post-content table, table.content +{ + margin-bottom: $spacing-vertical; + word-wrap: break-word; + max-width: 100%; + + th, td { - margin-bottom: $spacing-vertical; - word-wrap: break-word; - max-width: 100%; - - th, td + padding: $spacing-vertical/2 8px; + } + th + { + font-weight: bold; + font-size: 0.9em; + } + td + { + vertical-align: top; + } + thead th, > tr:first-child th + { + vertical-align: bottom; + font-weight: bold; + font-size: 0.9em; + padding: $spacing-vertical/4+1 8px $spacing-vertical/4-2; + text-align: left; + border-bottom: 1px solid #e2e2e2; + img { - padding: $spacing-vertical/2 8px; + vertical-align: text-bottom; } - th + } + tr.thead:not(:first-child) th + { + border-top: 1px solid #e2e2e2; + } + tfoot td + { + padding: $spacing-vertical / 2 8px; + font-size: .85em; + } + tbody + { + tr { - font-weight: bold; - font-size: 0.9em; - } - td - { - vertical-align: top; - } - thead th, > tr:first-child th - { - vertical-align: bottom; - font-weight: bold; - font-size: 0.9em; - padding: $spacing-vertical/4+1 8px $spacing-vertical/4-2; - text-align: left; - border-bottom: 1px solid #e2e2e2; - img + &:nth-child(even):not(.odd) { - vertical-align: text-bottom; - } - } - tr.thead:not(:first-child) th - { - border-top: 1px solid #e2e2e2; - } - tfoot td - { - padding: $spacing-vertical / 2 8px; - font-size: .85em; - } - tbody - { - tr - { - &:nth-child(even):not(.odd) - { - background-color: #f4f4f4; - } - &:nth-child(odd):not(.even) - { - background-color: white; - } - &.thead - { - background: none; - } - td - { - border: 0 none; - } + background-color: #f4f4f4; + } + &:nth-child(odd):not(.even) + { + background-color: white; + } + &.thead + { + background: none; + } + td + { + border: 0 none; } } + } - &:last-child - { - margin-bottom: 0; - } + &:last-child + { + margin-bottom: 0; + } + + &.full-table + { + width: 100%; } }