From 768274501580003c17de2ba6b26b0cd555678d2d Mon Sep 17 00:00:00 2001 From: Timothy Elias Date: Tue, 5 Mar 2024 08:55:18 +0100 Subject: [PATCH 1/2] TIM-97: Restructure code, keep old functionality - Add function that returns queryBuilder instead of queryResults - Add offset option to query --- .../Repository/EntryRepository.php | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/src/Netresearch/TimeTrackerBundle/Repository/EntryRepository.php b/src/Netresearch/TimeTrackerBundle/Repository/EntryRepository.php index c15d4400e..d2080dd78 100644 --- a/src/Netresearch/TimeTrackerBundle/Repository/EntryRepository.php +++ b/src/Netresearch/TimeTrackerBundle/Repository/EntryRepository.php @@ -317,7 +317,6 @@ public function getEntriesByUser($userId, $days = 3, $showFuture = true) return $data; } - /** * Get array of entries of given user and ticketsystem which should be synced to the ticketsystem. * Ordered by date, starttime desc @@ -486,9 +485,8 @@ public function getWorkByUser($userId, $period = self::PERIOD_DAY) return $data; } - /** - * Get array of entries for given filter params + * Get query of entries for given filter params * * @param array $arFilter every value is optional * @@ -504,10 +502,10 @@ public function getWorkByUser($userId, $period = self::PERIOD_DAY) * [maxResults] => int max number of returned datasets * [visibility_user] => user_id restricts entry visibility by users teams * - * @return array + * @return \Doctrine\ORM\Query * @throws \Exception */ - public function findByFilterArray($arFilter = []) + public function queryByFilterArray($arFilter = []) { $queryBuilder = $this->createQueryBuilder('e'); @@ -573,13 +571,44 @@ public function findByFilterArray($arFilter = []) ->setMaxResults((int) $arFilter['maxResults']); } + //pagination offset + if (isset($arFilter['page']) && isset($arFilter['maxResults'])) { + $queryBuilder + ->setFirstResult((int) $arFilter['page'] * $arFilter['maxResults']); + } + if (isset($arFilter['visibility_user']) && !is_null($arFilter['visibility_user'])) { $queryBuilder ->andWhere('e.user = :vis_user') ->setParameter('vis_user', (int) $arFilter['visibility_user']); } - return $queryBuilder->getQuery()->getResult(); + return $queryBuilder->getQuery(); + } + + /** + * Get array of entries for given filter params + * + * @param array $arFilter every value is optional + * + * $arFilter[customer] => int customer_id + * [project] => int project_id + * [user] => int user_id + * [activity] => int activity_id + * [team] => int team_id + * [datestart] => string + * [dateend] => string + * [ticket] => string + * [description] => string + * [maxResults] => int max number of returned datasets + * [visibility_user] => user_id restricts entry visibility by users teams + * + * @return array + * @throws \Exception + */ + public function findByFilterArray($arFilter = []) + { + return $this->queryByFilterArray($arFilter)->getResult(); } /** From a1b7405732d7537fc3f174335d8b5c3363b143ea Mon Sep 17 00:00:00 2001 From: Timothy Elias Date: Mon, 4 Mar 2024 16:56:31 +0100 Subject: [PATCH 2/2] TIM-97: Add new function getAllEntriesAction() - function takes a array of parameters and returns entries filtered by parameter --- .../Controller/InterpretationController.php | 114 ++++++++++++++ .../Resources/config/routing.yml | 4 + web/api.yml | 147 ++++++++++++++++++ 3 files changed, 265 insertions(+) diff --git a/src/Netresearch/TimeTrackerBundle/Controller/InterpretationController.php b/src/Netresearch/TimeTrackerBundle/Controller/InterpretationController.php index f2c2590b1..4f38b6909 100644 --- a/src/Netresearch/TimeTrackerBundle/Controller/InterpretationController.php +++ b/src/Netresearch/TimeTrackerBundle/Controller/InterpretationController.php @@ -3,10 +3,12 @@ namespace Netresearch\TimeTrackerBundle\Controller; use Netresearch\TimeTrackerBundle\Entity\Entry; +use Netresearch\TimeTrackerBundle\Response\Error; use Netresearch\TimeTrackerBundle\Entity\User; use Netresearch\TimeTrackerBundle\Helper\TimeHelper; use Netresearch\TimeTrackerBundle\Model\JsonResponse; use Netresearch\TimeTrackerBundle\Model\Response; +use Doctrine\ORM\Tools\Pagination\Paginator; use Symfony\Component\HttpFoundation\Request; class InterpretationController extends BaseController @@ -455,4 +457,116 @@ private function normalizeData(array $data) return $normalized; } + /** + * Retrieves filtered time tracker entries based on request parameters. + * Applies pagination. + * + * @param Request $request + * @return \Netresearch\TimeTrackerBundle\Model\JsonResponse|Error + * @throws \Exception + */ + public function getAllEntriesAction(Request $request) + { + if (false === $this->isPl($request)) { + return $this->getFailedAuthorizationResponse(); + } + + $project = (int) $request->get('project_id'); + $datestart = $request->get('datestart'); + $dateend = $request->get('dateend'); + $customer = (int) $request->get('customer_id'); + $activity = (int) $request->get('activity_id'); + $maxResults = (int) $request->get('maxResults'); + $page = (int) $request->get('page'); + + //prepare data + if ($page < 0) { + $message = $this->get('translator')->trans('page can not be negative.'); + return new Error($message, 400); + } + $maxResults = $maxResults > 0 ? $maxResults : 50; //cant be lower than 0 + + $searchArray = [ + 'maxResults' => $maxResults, //default 50 + 'page' => $page + ]; + // parameter has to exist in request and has to be different from null -> return true -> set array key + ($activity ?? null) ? $searchArray['activity'] = $activity : null; + ($project ?? null) ? $searchArray['project'] = $project : null; + ($datestart ?? null) ? $searchArray['datestart'] = $datestart : null; + ($dateend ?? null) ? $searchArray['dateend'] = $dateend : null; + ($customer ?? null) ? $searchArray['customer'] = $customer : null; + + $repository = $this->getDoctrine()->getRepository('NetresearchTimeTrackerBundle:Entry'); + try { + $paginator = new Paginator($repository->queryByFilterArray($searchArray)); + } catch (\Exception $e) { + $response = new Response($this->translate($e->getMessage())); + $response->setStatusCode(406); + } + + // get data + $entries = $paginator->getQuery()->getResult(); + $entryList = array(); + foreach ($entries as $entry) { + $flatEntry = $entry->toArray(); + unset($flatEntry['class']); + $flatEntry['date'] = $entry->getDay() ? $entry->getDay()->format('Y-m-d') : null; + + // add id suffix to if parameter + $flatEntry['user_id'] = $flatEntry['user']; + $flatEntry['project_id'] = $flatEntry['project']; + $flatEntry['customer_id'] = $flatEntry['customer']; + $flatEntry['activity_id'] = $flatEntry['activity']; + $flatEntry['worklog_id'] = $flatEntry['worklog']; + // unset old keys + unset($flatEntry['user']); + unset($flatEntry['project']); + unset($flatEntry['customer']); + unset($flatEntry['activity']); + unset($flatEntry['worklog']); + + // build result + $entryList[] = $flatEntry; + } + + // build url + parse_str($request->getQueryString(), $queryString); + unset($queryString['page']); + $queryString = '?' . http_build_query($queryString); + $route = $request->getUriForPath($request->getPathInfo()). $queryString; + + // negative firstResult are interpreted as 0 + $total = $paginator->count(); + + //self + $self = $route . '&' . http_build_query(['page' => $page]); + + // returns null for empty Paginator, else returns last page for given $maxResults + $lastPage = ceil($total / $maxResults) - 1; + $last = $total + ? $route . '&' . http_build_query(['page' => $lastPage]) + : null; + + // returns the last previous page with data, or null if you are on page 0 or there is no data + $prev = $page && $total + ? $route . '&' . http_build_query(['page' => min($page - 1, $lastPage)]) + : null; + + //null when query would return empty data + $next = $page < $lastPage + ? $route . '&' . http_build_query(['page' => $page + 1]) + : null; + + $links = [ + 'links' => [ + 'self' => $self, + 'last' => $last, + 'prev' => $prev, + 'next' => $next, + ], + ]; + $entryList = array_merge($links, ['data' => $entryList]); + return new JsonResponse($entryList); + } } diff --git a/src/Netresearch/TimeTrackerBundle/Resources/config/routing.yml b/src/Netresearch/TimeTrackerBundle/Resources/config/routing.yml index 98030ca29..88b3d7fd9 100644 --- a/src/Netresearch/TimeTrackerBundle/Resources/config/routing.yml +++ b/src/Netresearch/TimeTrackerBundle/Resources/config/routing.yml @@ -38,6 +38,10 @@ interpretation_entries: path: /interpretation/entries defaults: { _controller: NetresearchTimeTrackerBundle:Interpretation:getLastEntries } +interpretation_all_entries: + path: /interpretation/allEntries + defaults: { _controller: NetresearchTimeTrackerBundle:Interpretation:getAllEntries } + timetracking_save: path: /tracking/save defaults: { _controller: NetresearchTimeTrackerBundle:Crud:save } diff --git a/web/api.yml b/web/api.yml index 420d510ba..1cdf2c1e7 100644 --- a/web/api.yml +++ b/web/api.yml @@ -1075,6 +1075,148 @@ paths: properties: $ref: '#/components/schemas/ChartDataSuccessResponse' + /interpretation/allEntries: + get: + summary: Get all entries filtered by parameter. Not set parameter will not be searched (e.g. not declaring a project will return all projects). Data is sorted by descending id. + tags: + - Entries + parameters: + - $ref: '#/components/parameters/datestart' + - $ref: '#/components/parameters/dateend' + - in: query + name: project_id + description: Project ID + schema: + type: integer + example: 1 + - in: query + name: customer_id + description: Customer ID + schema: + type: integer + example: 1 + - in: query + name: activity_id + description: Activity ID + schema: + type: integer + example: 1 + - in: query + name: maxResults + description: Number of returned entries, default = 50 + schema: + type: integer + example: 50 + - in: query + name: page + description: Offset + schema: + type: integer + example: 0 + responses: + '406': + description: Error Not Acceptable + content: + application/json: + schema: + type: string + example: Error for databasequery + '403': + description: Error Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ForbiddenResponse' + '401': + description: Error Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/UnauthorizedResponse' + '400': + description: invalid parameter value + content: + application/json: + schema: + type: object + properties: + message: + type: string + example: 'page can not be negative.' + '200': + description: OK + content: + application/json: + schema: + type: object + properties: + links: + type: object + properties: + self: + type: string + last: + type: string + prev: + type: string + description: url of previous entries set or null if no previous entries + next: + type: string + description: url of next entries set or null if no more entries + data: + type: array + items: + type: object + properties: + id: + type: integer + date: + type: string + format: date + example: "2000-01-30" + start: + type: string + format: time + example: "08:00" + end: + type: string + format: time + example: "10:40" + description: + type: string + example: "Backen" + ticket: + type: string + example: "FG-33" + duration: + type: integer + description: The duration in minutes + example: 160 + durationString: + type: string + description: The duration in string format (hh:mm) + example: "02:40" + user_id: + type: integer + example: 2 + project_id: + type: integer + example: 12 + nullable: true + customer_id: + type: integer + example: 1 + activity_id: + type: integer + example: 1 + nullable: true + description: Activity record ID, see /getActivities + worklog_id: + type: integer + nullable: true + description: ID of Jira work log entry + example: null + /login: post: summary: login @@ -2425,6 +2567,7 @@ components: activity: in: query name: activity + description: Activity ID schema: type: integer example: 2 @@ -2432,6 +2575,7 @@ components: customer: in: query name: customer + description: Customer ID schema: type: integer example: 1 @@ -2439,6 +2583,7 @@ components: dateend: in: query name: dateend + description: Entries untill this date schema: type: string format: date @@ -2447,6 +2592,7 @@ components: datestart: in: query name: datestart + description: Entries from this date onwards schema: type: string format: date @@ -2462,6 +2608,7 @@ components: project: in: query name: project + description: Project ID schema: type: integer example: 2