diff --git a/generated/harvest-openapi.yaml b/generated/harvest-openapi.yaml index 7fa097c..cc3b505 100644 --- a/generated/harvest-openapi.yaml +++ b/generated/harvest-openapi.yaml @@ -4506,6 +4506,8 @@ paths: responses: 200: description: 'Clients Report' + schema: + $ref: '#/definitions/ExpenseReportsResult' default: description: 'error payload' schema: @@ -4550,6 +4552,8 @@ paths: responses: 200: description: 'Projects Report' + schema: + $ref: '#/definitions/ExpenseReportsResult' default: description: 'error payload' schema: @@ -4594,6 +4598,8 @@ paths: responses: 200: description: 'Expense Categories Report' + schema: + $ref: '#/definitions/ExpenseReportsResult' default: description: 'error payload' schema: @@ -4638,6 +4644,8 @@ paths: responses: 200: description: 'Team Report' + schema: + $ref: '#/definitions/ExpenseReportsResult' default: description: 'error payload' schema: @@ -4682,6 +4690,8 @@ paths: responses: 200: description: 'Uninvoiced Report' + schema: + $ref: '#/definitions/UninvoicedReportResult' default: description: 'error payload' schema: @@ -4726,6 +4736,8 @@ paths: responses: 200: description: 'Clients Report' + schema: + $ref: '#/definitions/TimeReportsResult' default: description: 'error payload' schema: @@ -4770,6 +4782,8 @@ paths: responses: 200: description: 'Projects Report' + schema: + $ref: '#/definitions/TimeReportsResult' default: description: 'error payload' schema: @@ -4814,6 +4828,8 @@ paths: responses: 200: description: 'Tasks Report' + schema: + $ref: '#/definitions/TimeReportsResult' default: description: 'error payload' schema: @@ -4858,6 +4874,8 @@ paths: responses: 200: description: 'Team Report' + schema: + $ref: '#/definitions/TimeReportsResult' default: description: 'error payload' schema: @@ -4890,6 +4908,8 @@ paths: responses: 200: description: 'Project Budget Report' + schema: + $ref: '#/definitions/ProjectBudgetReportResult' default: description: 'error payload' schema: @@ -6310,7 +6330,146 @@ definitions: type: string description: 'Date and time the user was last updated.' format: date-time - Result: + ExpenseReportsResult: + type: object + externalDocs: + description: result + url: 'https://help.getharvest.com/api-v2/reports-api/reports/expense-reports/#the-result-object' + properties: + client_id: + type: integer + description: 'The ID of the client associated with the reported expenses. Only returned in the Client and Project reports.' + format: int32 + client_name: + type: string + description: 'The name of the client associated with the reported expenses. Only returned in the Client and Project reports.' + project_id: + type: integer + description: 'The ID of the project associated with the reported expenses. Only returned in the Client and Project reports.' + format: int32 + project_name: + type: string + description: 'The name of the project associated with the reported expenses. Only returned in the Client and Project reports.' + expense_category_id: + type: integer + description: 'The ID of the expense category associated with the reported expenses. Only returned in the Expense Category report.' + format: int32 + expense_category_name: + type: string + description: 'The name of the expense category associated with the reported expenses. Only returned in the Expense Category report.' + user_id: + type: integer + description: 'The ID of the user associated with the reported expenses. Only returned in the Team report.' + format: int32 + user_name: + type: string + description: 'The name of the user associated with the reported expenses. Only returned in the Team report.' + is_contractor: + type: boolean + description: 'The contractor status of the user associated with the reported expenses. Only returned in the Team report.' + total_amount: + type: number + description: 'The totaled cost for all expenses for the given timeframe, subject (client, project, expense category, or user), and currency.' + format: float + billable_amount: + type: number + description: 'The totaled cost for billable expenses for the given timeframe, subject (client, project, expense category, or user), and currency.' + format: float + currency: + type: string + description: 'The currency code associated with the expenses for this result.' + UninvoicedReportResult: + type: object + externalDocs: + description: result + url: 'https://help.getharvest.com/api-v2/reports-api/reports/uninvoiced-report/#the-result-object' + properties: + client_id: + type: integer + description: 'The ID of the client associated with the reported hours and expenses.' + format: int32 + client_name: + type: string + description: 'The name of the client associated with the reported hours and expenses.' + project_id: + type: integer + description: 'The ID of the project associated with the reported hours and expenses.' + format: int32 + project_name: + type: string + description: 'The name of the project associated with the reported hours and expenses.' + currency: + type: string + description: 'The currency code associated with the tracked hours for this result.' + total_hours: + type: number + description: 'The total hours for the given timeframe and project. If Time Rounding is turned on, the hours will be rounded according to your settings.' + format: float + uninvoiced_hours: + type: number + description: 'The total hours for the given timeframe and project that have not been invoiced. If Time Rounding is turned on, the hours will be rounded according to your settings.' + format: float + uninvoiced_expenses: + type: number + description: 'The total amount for billable expenses for the timeframe and project that have not been invoiced.' + format: float + uninvoiced_amount: + type: number + description: 'The total amount (time and expenses) for the timeframe and project that have not been invoiced.' + format: float + TimeReportsResult: + type: object + externalDocs: + description: result + url: 'https://help.getharvest.com/api-v2/reports-api/reports/time-reports/#the-result-object' + properties: + client_id: + type: integer + description: 'The ID of the client associated with the reported hours. Only returned in the Client and Project reports.' + format: int32 + client_name: + type: string + description: 'The name of the client associated with the reported hours. Only returned in the Client and Project reports.' + project_id: + type: integer + description: 'The ID of the project associated with the reported hours. Only returned in the Client and Project reports.' + format: int32 + project_name: + type: string + description: 'The name of the project associated with the reported hours. Only returned in the Client and Project reports.' + task_id: + type: integer + description: 'The ID of the task associated with the reported hours. Only returned in the Task report.' + format: int32 + task_name: + type: string + description: 'The name of the task associated with the reported hours. Only returned in the Task report.' + user_id: + type: integer + description: 'The ID of the user associated with the reported hours. Only returned in the Team report.' + format: int32 + user_name: + type: string + description: 'The name of the user associated with the reported hours. Only returned in the Team report.' + is_contractor: + type: boolean + description: 'The contractor status of the user associated with the reported hours. Only returned in the Team report.' + total_hours: + type: number + description: 'The totaled hours for the given timeframe, subject (client, project, task, or user), and currency. If Time Rounding is turned on, the hours will be rounded according to your settings.' + format: float + billable_hours: + type: number + description: 'The totaled billable hours for the given timeframe, subject (client, project, task, or user), and currency. If Time Rounding is turned on, the hours will be rounded according to your settings.' + format: float + currency: + type: string + description: 'The currency code associated with the tracked hours for this result. Only visible to Administrators and Project Managers with the View billable rates and amounts permission.' + billable_amount: + type: number + description: 'The totaled billable amount for the billable hours above. Only visible to Administrators and Project Managers with the View billable rates and amounts permission.' + format: float + ProjectBudgetReportResult: type: object externalDocs: description: result @@ -7287,7 +7446,115 @@ definitions: format: int64 links: $ref: '#/definitions/PaginationLinks' - Results: + ExpenseReportsResults: + type: object + required: + - results + - per_page + - total_pages + - total_entries + - next_page + - previous_page + - page + - links + properties: + results: + type: array + items: + $ref: '#/definitions/ExpenseReportsResult' + per_page: + type: integer + format: int64 + total_pages: + type: integer + format: int64 + total_entries: + type: integer + format: int64 + next_page: + type: integer + format: int64 + previous_page: + type: integer + format: int64 + page: + type: integer + format: int64 + links: + $ref: '#/definitions/PaginationLinks' + UninvoicedReportResults: + type: object + required: + - results + - per_page + - total_pages + - total_entries + - next_page + - previous_page + - page + - links + properties: + results: + type: array + items: + $ref: '#/definitions/UninvoicedReportResult' + per_page: + type: integer + format: int64 + total_pages: + type: integer + format: int64 + total_entries: + type: integer + format: int64 + next_page: + type: integer + format: int64 + previous_page: + type: integer + format: int64 + page: + type: integer + format: int64 + links: + $ref: '#/definitions/PaginationLinks' + TimeReportsResults: + type: object + required: + - results + - per_page + - total_pages + - total_entries + - next_page + - previous_page + - page + - links + properties: + results: + type: array + items: + $ref: '#/definitions/TimeReportsResult' + per_page: + type: integer + format: int64 + total_pages: + type: integer + format: int64 + total_entries: + type: integer + format: int64 + next_page: + type: integer + format: int64 + previous_page: + type: integer + format: int64 + page: + type: integer + format: int64 + links: + $ref: '#/definitions/PaginationLinks' + ProjectBudgetReportResults: type: object required: - results @@ -7302,7 +7569,7 @@ definitions: results: type: array items: - $ref: '#/definitions/Result' + $ref: '#/definitions/ProjectBudgetReportResult' per_page: type: integer format: int64 diff --git a/src/Extractor/Extractor.php b/src/Extractor/Extractor.php index ef75959..139c6d0 100644 --- a/src/Extractor/Extractor.php +++ b/src/Extractor/Extractor.php @@ -192,7 +192,7 @@ public static function buildDefinitionProperty($name, $type, $description) return $property; } - public function buildPath($url, $path, $method, $node) + public function buildPath($url, $path, $method, $node, $title) { $description = []; $parentNode = $node->parents()->filter('.highlighter-rouge')->first(); @@ -245,7 +245,7 @@ public function buildPath($url, $path, $method, $node) ], ], 'parameters' => self::buildPathParameters($method, $pathParameters, $explicitParameters, $explicitParametersColumns), - 'responses' => self::buildPathResponse($method, $summary), + 'responses' => self::buildPathResponse($method, $summary, $title), ]; } @@ -258,7 +258,7 @@ public static function buildPathParameters($method, $pathParameters, $explicitPa } if (\count($explicitParameters) > 0) { - if (in_array($method, ['patch', 'post'])) { + if (\in_array($method, ['patch', 'post'], true)) { $parameters[] = self::buildPathBodyParameter($explicitParameters, $explicitParametersColumns); } else { while (\count($explicitParameters) > 0) { @@ -331,7 +331,7 @@ public static function buildPathQueryParameter($name, $type, $description, $requ return [ 'name' => $name, 'description' => $description, - 'required' => ($required === 'required'), + 'required' => ('required' === $required), 'in' => 'query', 'type' => self::convertType($type), ]; @@ -345,12 +345,12 @@ public static function buildOperationId($method, $summary) return lcfirst(self::camelize($summary)); } - public static function buildPathResponse($method, $summary) + public static function buildPathResponse($method, $summary, $title) { $successResponse = [ 'description' => $summary, ]; - $successResponseSchema = self::guessPathResponseSchema($summary); + $successResponseSchema = self::guessPathResponseSchema($summary, $title); if ($successResponseSchema) { $successResponse['schema'] = [ @@ -443,9 +443,9 @@ public static function guessFieldType($name, $objectName = null) return 'string'; } - public static function guessPathResponseSchema($summary) + public static function guessPathResponseSchema($summary, $title) { - $guesser = function ($summary) { + $guesser = function ($summary) use ($title) { if (preg_match('/^Create an? ([a-zA-Z ]+)/', $summary, $matches)) { return '#/definitions/'.self::camelize($matches[1]); } @@ -470,6 +470,10 @@ public static function guessPathResponseSchema($summary) return '#/definitions/'.self::camelize($matches[1]); } + if (preg_match('/^([a-zA-Z ]+) Report/', $summary)) { + return '#/definitions/'.$title.'Result'; + } + if ('Retrieve the currently authenticated user' === $summary) { return '#/definitions/User'; } @@ -509,6 +513,10 @@ public static function pluralize($word) return substr($word, 0, \strlen($word) - 1).'ies'; } + if (self::endsWith($word, 'result')) { + return 'results'; + } + return $word.'s'; } @@ -657,9 +665,17 @@ private function extractApiDoc($url) { $crawler = new Crawler(file_get_contents($url)); - $crawler->filter('h2[id^="the-"][id$="-object"]')->each(function (Crawler $node, $i) use ($url) { + $title = trim($crawler->filter('h1')->text()); + + if (preg_match('/^([a-zA-Z ]+) Report(s)?/', $title)) { + $title = self::camelize($title); + } else { + $title = ''; + } + + $crawler->filter('h2[id^="the-"][id$="-object"]')->each(function (Crawler $node, $i) use ($url, $title) { if (preg_match('/^the-(.+)-object$/', $node->attr('id'), $matches)) { - $definitionName = self::camelize($matches[1]); + $definitionName = $title.self::camelize($matches[1]); $this->definitions[$definitionName] = [ 'type' => 'object', 'externalDocs' => [ @@ -676,7 +692,7 @@ private function extractApiDoc($url) } }); - $crawler->filter('div.highlighter-rouge pre.highlight code')->each(function (Crawler $node, $i) use ($url) { + $crawler->filter('div.highlighter-rouge pre.highlight code')->each(function (Crawler $node, $i) use ($url, $title) { $text = trim($node->text()); if (preg_match('/^(GET|POST|PATCH|DELETE) \/v2(\/.*)/', $text, $matches)) { @@ -689,7 +705,7 @@ private function extractApiDoc($url) $this->paths[$path] = []; } - $this->paths[$path][$method] = self::buildPath($url, $path, $method, $node); + $this->paths[$path][$method] = self::buildPath($url, $path, $method, $node, $title); } }); }