diff --git a/CMakeLists.txt b/CMakeLists.txt index 7040f07..72b67df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,6 +85,12 @@ SET(REDMINE_API_SOURCES include/Redmine/API.hpp src/Redmine/API.cpp + + include/Redmine/Filter.hpp + src/Redmine/Filter.cpp + + include/Redmine/Issue.hpp + src/Redmine/Issue.cpp ) add_library(redmine-api-cpp-objlib OBJECT ${REDMINE_API_SOURCES}) @@ -129,6 +135,8 @@ add_executable(redmine-cli src/Redmine-CLI/Command/Project.cpp src/Redmine-CLI/Command/IssueStatus.hpp src/Redmine-CLI/Command/IssueStatus.cpp + src/Redmine-CLI/Command/Issue.hpp + src/Redmine-CLI/Command/Issue.cpp ) target_link_libraries(redmine-cli redmine-api-cpp-static loguru CLI11 tableprinter::tableprinter) target_include_directories(redmine-cli diff --git a/include/Redmine/API.hpp b/include/Redmine/API.hpp index e614371..6821442 100644 --- a/include/Redmine/API.hpp +++ b/include/Redmine/API.hpp @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +#include "Redmine/Filter.hpp" +#include "Redmine/Issue.hpp" #include "Redmine/IssueStatus.hpp" #include "nlohmann/json_fwd.hpp" #include @@ -127,9 +129,19 @@ namespace Redmine * * @return std::vector */ - std::vector getProjects() const; + std::vector getProjects(const std::uint32_t limit) const; void uploadFileToProject(const std::uint64_t projectId, const std::string &filePath, const std::string &fileName, const std::string &description, const std::uint32_t version) const; + + /** + * @brief Get issues with the given filters from the redmine server + * + * @param filter - the filter to apply on the issues + * @return std::vector + */ + std::vector getIssues(const Redmine::Filter& filter = {}) const; + + /*@}*/ }; diff --git a/include/Redmine/Filter.hpp b/include/Redmine/Filter.hpp new file mode 100644 index 0000000..7de4c74 --- /dev/null +++ b/include/Redmine/Filter.hpp @@ -0,0 +1,64 @@ +#pragma once +/* +* Copyright (C) 2024 Dominik Meyer +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +/** + * @brief The main namespace of this library for Redmine related datatypes, classes, and functions + * + */ +#include +#include +#include +namespace Redmine +{ + /** + * @brief Class for filering data in a Redmine API query + * + */ + class Filter + { + private: + + /// filter the issued by project id + bool filterByProjectID_; + /// the project ID to filter + std::uint64_t projectID_; + + /// possible project stati for a filter + enum class ProjectStatus {open, closed, both}; + + /// the project status to filter by + ProjectStatus projectStatus_; + + /// whether to filter by assigned user or not + bool filterByAssignedUser_; + + /// the user ID to filter by + std::uint64_t assignedUserID_; + + public: + + Filter(); + + void filterByProjectID(const std::uint64_t id); + void setProjectStatus(const ProjectStatus& status); + void filterByAssignedUserId(const std::uint64_t id); + std::string toQueryString() const; + + }; + +}; // namespace Redmine \ No newline at end of file diff --git a/include/Redmine/Issue.hpp b/include/Redmine/Issue.hpp index 0692e67..099fc4a 100644 --- a/include/Redmine/Issue.hpp +++ b/include/Redmine/Issue.hpp @@ -63,10 +63,20 @@ namespace Redmine */ nlohmann::json get() const; - + /** + * @brief return the id of the issue + * + * @return std::uint64_t + */ std::uint64_t getId() const; - + std::string getProject() const; + std::uint64_t getProjectId() const; + std::string getStatus() const; + std::uint64_t getStatusID() const; + std::string getSubject() const; + std::string getDescription() const; + std::string to_string() const; }; // class Issue diff --git a/include/Redmine/Tracker.hpp b/include/Redmine/Tracker.hpp index e69de29..0a131c2 100644 --- a/include/Redmine/Tracker.hpp +++ b/include/Redmine/Tracker.hpp @@ -0,0 +1,79 @@ +#pragma once +/* +* Copyright (C) 2024 Dominik Meyer +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include "nlohmann/json_fwd.hpp" +#include +#include +#include +#include + +/** + * @brief The main namespace of this library for Redmine related datatypes, classes, and functions + * + */ +namespace Redmine +{ + + /** + * @brief This class represents an Issue Tracker within the redmine server. + * + */ + class Tracker : public Redmine::Object + { + private: + /// the data store for the issue + nlohmann::json data_; + + /** + * @brief verify issue data + * + * @param data - the json object containing the issue data + */ + void _verify(const nlohmann::json &data); + + public: + explicit Tracker(const nlohmann::json &issue); + + /** + * @brief set the issue object from a json object + * + * @param data + */ + void set(const nlohmann::json &data); + + /** + * @brief return a json object from the issue object + * + * @return nlohmann::json + */ + nlohmann::json get() const; + + /** + * @brief Get the Id of the tracker + * + * @return std::uint64_t + */ + std::uint64_t getId() const; + + + + + + }; // class Issue + +}; // namespace Redmine \ No newline at end of file diff --git a/src/Redmine-CLI/Command/Issue.cpp b/src/Redmine-CLI/Command/Issue.cpp new file mode 100644 index 0000000..438e759 --- /dev/null +++ b/src/Redmine-CLI/Command/Issue.cpp @@ -0,0 +1,116 @@ +/* +* Copyright (C) 2024 Dominik Meyer +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include +#include +#include "Redmine/API.hpp" +#include "Redmine/Filter.hpp" +#include "nlohmann/json_fwd.hpp" +#include + +Command::Issue::Issue(RedmineCLI::Redmine *r) +: +redmine_(r), +output_(TEXT), +projectId_(-1), +userId_(-1) +{ + + CLI::App* issue = redmine_->getCLI().add_subcommand("issue", + "Api Calls on issues accessible by token owner")->require_subcommand(); + + + list_ = issue->add_subcommand("list", "List issues accessible by the token owner"); + list_->needs(redmine_->getTokenOption())->needs(redmine_->getUrlOption()); + list_->callback([&](){getList_();}); + list_->add_option("-o,--output", output_, "The Output Format to use. Supported: TEXT, YAML")->transform(CLI::CheckedTransformer(outputMap, CLI::ignore_case)); + list_->add_option("-p,--project",projectId_,"Filter issues by project id"); + list_->add_option("--userid", userId_,"Filter issues by user id"); +} + +void Command::Issue::printIssueListText_(const std::vector &issues) const +{ + tableprinter::printer p + { + { + { tableprinter::name { "ID" } , tableprinter::width { 8 } } , + { tableprinter::name { "Subject" } , tableprinter::width { 80 } } , + { tableprinter::name { "Project" } , tableprinter::width { 25 } } , + {tableprinter::name { "Status" }, tableprinter::width { 15 }} + } , + { std::cout } + }; + + p.sanity_check(); + p.print_headers(); + + for (auto it = issues.begin(); it!= issues.end(); ++it) + { + p.print(it->getId(), it->getSubject(), it->getProject(), it->getStatus()); + } + +} + +void Command::Issue::printIssueListJson_(const std::vector &issues) const +{ + nlohmann::json data = nlohmann::json::array(); + + for (auto it = issues.begin(); it!= issues.end(); ++it) + { + nlohmann::json issue; + issue["id"] = it->getId(); + issue["subject"] = it->getSubject(); + issue["project"] = it->getProject(); + issue["project_id"] = it->getProjectId(); + issue["status"] = it->getStatus(); + issue["status_id"] = it->getStatusID(); + + data.push_back(issue); + } + + std::cout << data.dump(2); +} + +void Command::Issue::getList_() const +{ + Redmine::API client{redmine_->getUrl(), redmine_->getToken()}; + + Redmine::Filter filter{}; + + if (projectId_ > -1) + { + filter.filterByProjectID(projectId_); + } + + if (userId_ > -1) + { + filter.filterByAssignedUserId(userId_); + } + + auto issues = client.getIssues(filter); + + if (output_ == TEXT) + { + printIssueListText_(issues); + } + else if (output_ == JSON) + { + printIssueListJson_(issues); + } + + return; +} \ No newline at end of file diff --git a/src/Redmine-CLI/Command/Issue.hpp b/src/Redmine-CLI/Command/Issue.hpp new file mode 100644 index 0000000..71f2ac6 --- /dev/null +++ b/src/Redmine-CLI/Command/Issue.hpp @@ -0,0 +1,99 @@ +#pragma once +/* +* Copyright (C) 2024 Dominik Meyer +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ +#include "Redmine/Filter.hpp" +#include "Redmine/Project.hpp" +#include "nlohmann/json_fwd.hpp" +#include +#include +#include +#include +#include +#include +#include +#define LOGURU_WITH_STREAMS 1 +#include +#include + + +// forward declaration +namespace RedmineCLI +{ + class Redmine; +}; + +namespace Command +{ + + /** + * @brief class for processing the CLI11 parsed command lines + * + */ + class Issue + { + + private: + + /// the main redmine cli application context + RedmineCLI::Redmine *redmine_; + + /// enum for identifying supported output types + enum outputType : std::uint16_t + { + /// output is just text + TEXT, + /// output is json + JSON + }; + + /// requested output type + outputType output_; + + std::map outputMap{{"text", TEXT}, {"json", JSON}}; + + /// vector to hold requested attribute fields + std::vector fields_; + + /// the project id to work on + std::int64_t projectId_; + + /// the userid to filter on + std::uint64_t userId_; + + /// the status to filter on + std::string status_; + + /// pointer to the list subcommand + CLI::App* list_; + + + void printIssueListJson_(const std::vector &issues) const; + + void printIssueListText_(const std::vector &issues) const; + + /// the actual implementation of the list command + void getList_() const; + + public: + /** + * @brief Construct a new CLIProcessor object + * + */ + explicit Issue(RedmineCLI::Redmine *r); + + }; // class Issue +}; // namespace Command \ No newline at end of file diff --git a/src/Redmine-CLI/Command/Project.cpp b/src/Redmine-CLI/Command/Project.cpp index 208c901..c92471a 100644 --- a/src/Redmine-CLI/Command/Project.cpp +++ b/src/Redmine-CLI/Command/Project.cpp @@ -19,12 +19,14 @@ #include #include "Redmine/API.hpp" #include "nlohmann/json_fwd.hpp" +#include #include Command::Project::Project(RedmineCLI::Redmine *r) : redmine_(r), -output_(TEXT) +output_(TEXT), +limit_(100) { CLI::App* project = redmine_->getCLI().add_subcommand("project", @@ -36,6 +38,7 @@ output_(TEXT) list_->callback([&](){getList_();}); list_->add_option("-f,--fields", fields_, "The fields of the project object to print. Supported: name, id"); list_->add_option("-o,--output", output_, "The Output Format to use. Supported: TEXT, YAML")->transform(CLI::CheckedTransformer(outputMap, CLI::ignore_case)); + list_->add_option("-l,--limit",limit_,"The maximum number of projects to return. (Default: " + std::to_string(limit_) +" )"); upload_ = project->add_subcommand("upload", "Upload a File to the Project"); @@ -81,8 +84,8 @@ void Command::Project::printProjectListText_(const std::vector { { { tableprinter::name { "ID" } , tableprinter::width { 8 } } , - { tableprinter::name { "Identifier" } , tableprinter::width { 20 } } , - { tableprinter::name { "Name" } , tableprinter::width { 30 } } , + { tableprinter::name { "Identifier" } , tableprinter::width { 30 } } , + { tableprinter::name { "Name" } , tableprinter::width { 40 } } , } , { std::cout } }; @@ -145,9 +148,9 @@ void Command::Project::getList_() const { Redmine::API client{redmine_->getUrl(), redmine_->getToken()}; - auto projects = client.getProjects(); + auto projects = client.getProjects(limit_); printProjectList_(projects); return; -} \ No newline at end of file +} diff --git a/src/Redmine-CLI/Command/Project.hpp b/src/Redmine-CLI/Command/Project.hpp index 49ec723..2646bb0 100644 --- a/src/Redmine-CLI/Command/Project.hpp +++ b/src/Redmine-CLI/Command/Project.hpp @@ -82,12 +82,19 @@ namespace Command /// the version of the uploading files std::uint32_t fileVersion_; + /// the maximum number of projects to list + std::uint32_t limit_; + /// pointer to the list subcommand CLI::App* list_; /// pointer to the upload subcommand CLI::App* upload_; + + /// point to the list issues subcommand + CLI::App* issuesList_; + /** * @brief method to print the project list * diff --git a/src/Redmine-CLI/Redmine.cpp b/src/Redmine-CLI/Redmine.cpp index c8bc010..6c5a5d7 100644 --- a/src/Redmine-CLI/Redmine.cpp +++ b/src/Redmine-CLI/Redmine.cpp @@ -40,5 +40,5 @@ version_(nullptr) myAccount_ = std::make_unique(this); project_ = std::make_unique(this); issueStatus_ = std::make_unique(this); - + issue_ = std::make_unique(this); } \ No newline at end of file diff --git a/src/Redmine-CLI/Redmine.hpp b/src/Redmine-CLI/Redmine.hpp index 070d94e..770dab0 100644 --- a/src/Redmine-CLI/Redmine.hpp +++ b/src/Redmine-CLI/Redmine.hpp @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "Redmine-CLI/Command/Issue.hpp" #include "Redmine-CLI/Command/IssueStatus.hpp" #include "Redmine-CLI/Command/MyAccount.hpp" #include "Redmine-CLI/Command/Project.hpp" @@ -63,6 +64,10 @@ namespace RedmineCLI /// pointer to the project subcommand std::unique_ptr project_; + + /// pointer to the issue subcommand + std::unique_ptr issue_; + public: /** diff --git a/src/Redmine/API.cpp b/src/Redmine/API.cpp index 93717db..9f3f515 100644 --- a/src/Redmine/API.cpp +++ b/src/Redmine/API.cpp @@ -14,13 +14,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "Redmine/Issue.hpp" #include "Redmine/IssueStatus.hpp" #include "Redmine/Project.hpp" #include "nlohmann/json_fwd.hpp" #include #include +#include #include #include +#include #include #include #include @@ -185,9 +188,9 @@ void Redmine::API::setMyAccount(const Redmine::User &user) const } -std::vector Redmine::API::getProjects() const +std::vector Redmine::API::getProjects(const std::uint32_t limit) const { - nlohmann::json projectList = get("/projects.json"); + nlohmann::json projectList = get("/projects.json?limit="+std::to_string(limit)); std::vector projects; for (auto it = projectList["projects"].begin(); it != projectList["projects"].end(); ++it) @@ -242,4 +245,17 @@ void Redmine::API::uploadFileToProject(const std::uint64_t projectId, const std: upload["file"] = file; post("/projects/"+std::to_string(projectId)+"/files.json", upload); -} \ No newline at end of file +} + + std::vector Redmine::API::getIssues(const Redmine::Filter& filter) const + { + std::vector issues; + + nlohmann::json issueList = get("/issues.json?" + filter.toQueryString()); + for (auto &issue : issueList["issues"]) + { + issues.push_back(Redmine::Issue(issue)); + } + + return issues; + } \ No newline at end of file diff --git a/src/Redmine/Filter.cpp b/src/Redmine/Filter.cpp new file mode 100644 index 0000000..e927996 --- /dev/null +++ b/src/Redmine/Filter.cpp @@ -0,0 +1,75 @@ +/* +* Copyright (C) 2024 Dominik Meyer +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ +#include +#include + +Redmine::Filter::Filter() + : + filterByProjectID_(false), + projectStatus_(Redmine::Filter::ProjectStatus::open), + filterByAssignedUser_(false) + { + + } + +void Redmine::Filter::filterByProjectID(const std::uint64_t id) +{ + filterByProjectID_ = true; + projectID_ = id; +} + +void Redmine::Filter::filterByAssignedUserId(const std::uint64_t id) +{ + filterByAssignedUser_ = true; + assignedUserID_ = id; +} + +std::string Redmine::Filter::toQueryString() const +{ + std::stringstream filter; + + if (filterByProjectID_) + { + filter << "project_id=" << projectID_ << "&"; + } + + switch (projectStatus_) + { + case Redmine::Filter::ProjectStatus::both: + filter << "status_id=*"; + break; + + case Redmine::Filter::ProjectStatus::open: + filter << "status=open"; + break; + + case Redmine::Filter::ProjectStatus::closed: + filter << "status=closed"; + break; + }; // switch projectStatus + + if (filterByAssignedUser_) + { + if (filter.rdbuf()->in_avail() > 0) + { + filter << "&"; + } + filter << "assigned_to_id=" << assignedUserID_; + } + + return filter.str(); +} \ No newline at end of file diff --git a/src/Redmine/Issue.cpp b/src/Redmine/Issue.cpp new file mode 100644 index 0000000..4b49506 --- /dev/null +++ b/src/Redmine/Issue.cpp @@ -0,0 +1,68 @@ +#include +#define LOGURU_WITH_STREAMS 1 +#include + + +Redmine::Issue::Issue(const nlohmann::json &issue) : Redmine::Object() +{ + set(issue); +} + +void Redmine::Issue::set(const nlohmann::json &data) +{ + _verify(data); + data_=data; +} + +nlohmann::json Redmine::Issue::get() const +{ + return data_; +} + +void Redmine::Issue::_verify(const nlohmann::json &data) +{ + + _baseVerify(data, ""); + +} + +std::uint64_t Redmine::Issue::getId() const +{ + return data_["id"].get(); +} + +std::string Redmine::Issue::getProject() const +{ + return data_["project"]["name"].get(); +} + +std::uint64_t Redmine::Issue::getProjectId() const +{ + return data_["project"]["id"].get(); +} + +std::string Redmine::Issue::getStatus() const +{ + return data_["status"]["name"].get(); +} + +std::uint64_t Redmine::Issue::getStatusID() const +{ + return data_["status"]["id"].get(); +} + +std::string Redmine::Issue::getSubject() const +{ + return data_["subject"].get(); +} + +std::string Redmine::Issue::getDescription() const +{ + return data_["description"].get(); +} + + +std::string Redmine::Issue::to_string() const +{ + return data_.dump(2); +} \ No newline at end of file diff --git a/src/config.hpp b/src/config.hpp index 3e4403e..101d459 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -1,5 +1,4 @@ -#ifndef __LIBCMS_CONFIG_HPP__ -#define __LIBCMS_CONFIG_HPP__ +#pragma once /* * Copyright (C) 2024 Dominik Meyer * @@ -24,5 +23,3 @@ #define VERSION_MINOR 0 #define VERSION_PATCH 1 #define PROJECT_VERSION "0.0.1" - -#endif