Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- 📋 **Управление проектами** - добавление и просмотр ваших проектов
- 🎯 **Создание задач** - интерактивное создание задач с выбором реальных типов из API
- 🔍 **Проверка соединения** - тестирование подключения к Jira
- 📌 **Мои задачи** - просмотр всех задач, назначенных на вас, прямо в терминале
- ⚡ **Быстрота** - мгновенное создание задач без ожидания загрузки веб-интерфейса

## 🚀 Установка
Expand Down Expand Up @@ -69,6 +70,7 @@ fast-task create
| `fast-task list-projects` | Просмотр настроенных проектов |
| `fast-task test` | Проверка соединения с Jira |
| `fast-task create` | Создание новой задачи |
| `fast-task my-issues` | Просмотр задач, назначенных на вас |

## 💡 Примеры использования

Expand All @@ -90,6 +92,32 @@ Configured projects:
MOBILE - Mobile App
```

### Просмотр назначенных задач
```bash
$ fast-task my-issues
🔍 Fetching your assigned issues...
Found 3 issue(s):

WEB-42 | In Progress | Fix responsive layout on mobile | https://company.atlassian.net/browse/WEB-42
WEB-57 | To Do | Add dark mode support | https://company.atlassian.net/browse/WEB-57
API-103 | In Review | Refactor auth middleware | https://company.atlassian.net/browse/API-103
```

Доступные флаги:
- `--project <KEY>` — фильтр по конкретному проекту
- `--all` — включить завершённые задачи (по умолчанию показываются только активные)

```bash
# Только задачи проекта WEB
$ fast-task my-issues --project WEB

# Все задачи, включая завершённые
$ fast-task my-issues --all

# Комбинация флагов
$ fast-task my-issues --project WEB --all
```

### Создание задачи
```bash
$ fast-task create
Expand Down
129 changes: 129 additions & 0 deletions src/jira_client.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,65 @@
use thiserror::Error;

use crate::config::Config;
use crate::jql::{self, Field, JqlBuilder, SortOrder};
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::json;

#[derive(Serialize, Deserialize, Debug)]
pub struct SearchResponse {
#[serde(rename = "startAt")]
pub start_at: u16,
#[serde(rename = "maxResults")]
pub max_results: u16,
pub total: u16,
pub issues: Vec<Issue>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Issue {
pub key: String,
pub fields: IssueFields,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct IssueFields {
pub summary: String,
pub status: IssueStatus,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct IssueStatus {
pub name: String,
#[serde(rename = "statusCategory")]
pub status_category: IssueStatusCategory,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct IssueStatusCategory {
pub key: String,
}

#[derive(Debug, Clone, Default)]
#[allow(dead_code)]
pub enum IssueStatusFilter {
/// Exclude done/resolved issues (default behavior).
#[default]
ActiveOnly,
/// Include all issues regardless of status.
All,
/// Show only issues with this specific status name.
Only(String),
}

#[derive(Debug, Clone, Default)]
pub struct MyIssuesQuery {
pub status_filter: IssueStatusFilter,
pub project: Option<String>,
}

const SEARCH_PAGE_SIZE: u16 = 50;

pub struct JiraClient {
client: Client,
config: Config,
Expand Down Expand Up @@ -134,6 +189,80 @@ pub async fn test_connection(client: &JiraClient) -> Result<(), JiraClientError>
}
}

pub async fn get_my_issues(
jira_client: &JiraClient,
query: &MyIssuesQuery,
) -> Result<Vec<Issue>, JiraClientError> {
let mut builder = JqlBuilder::new().assignee_is_current_user();
match &query.status_filter {
IssueStatusFilter::ActiveOnly => {
builder = builder.exclude_done();
}
IssueStatusFilter::All => {}
IssueStatusFilter::Only(status) => {
builder = builder.and(
Field::Status,
jql::Operator::Eq,
jql::Value::Str(status.clone()),
);
}
}
if let Some(project_key) = &query.project {
builder = builder
.project(project_key)
.map_err(|e| JiraClientError::Request(e.to_string()))?;
}
let jql = builder.order_by(Field::Updated, SortOrder::Desc).build();

let api_url = format!(
"{}/rest/api/2/search",
Copy link

Choose a reason for hiding this comment

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

nit: можно использовать тип Url и соединять в него пути вместо форматирования строчек.

jira_client.config.jira_url.trim_end_matches('/')
);

let mut all_issues = Vec::new();
let max_results = SEARCH_PAGE_SIZE;
let mut start_at = 0;

loop {
let body = json!({
"jql": jql,
"startAt": start_at,
"maxResults": max_results,
"fields": ["summary", "status"]
});

let response = jira_client
.client
.post(&api_url)
.header("Authorization", &jira_client.auth_header)
.header("Content-Type", "application/json")
.json(&body)
.send()
.await
.map_err(|err| JiraClientError::Request(err.to_string()))?;

if !response.status().is_success() {
return Err(JiraClientError::Response(
response.status(),
response.text().await.unwrap_or_default(),
));
}

let search_response: SearchResponse =
response.json().await.map_err(|_| JiraClientError::Parse)?;

let fetched = search_response.issues.len() as u16;
all_issues.extend(search_response.issues);
start_at += fetched;

if start_at >= search_response.total || fetched == 0 {
break;
}
}

Ok(all_issues)
}

pub async fn get_project_issue_types(
jira_client: &JiraClient,
project_key: &str,
Expand Down
Loading
Loading