|
5 | 5 |
|
6 | 6 | from thefuzz import fuzz |
7 | 7 |
|
| 8 | +from mcp_atlassian.models.jira.field_option import ( |
| 9 | + JiraFieldContextOptionsResponse, |
| 10 | + JiraFieldContextsResponse, |
| 11 | + JiraFieldOptionsResponse, |
| 12 | +) |
| 13 | + |
8 | 14 | from .client import JiraClient |
9 | 15 | from .protocols import EpicOperationsProto, UsersOperationsProto |
10 | 16 |
|
@@ -524,3 +530,228 @@ def similarity(keyword: str, field: dict) -> int: |
524 | 530 | except Exception as e: |
525 | 531 | logger.error(f"Error searching fields: {str(e)}") |
526 | 532 | return [] |
| 533 | + |
| 534 | + def get_field_contexts( |
| 535 | + self, |
| 536 | + field_id: str, |
| 537 | + start_at: int = 0, |
| 538 | + max_results: int = 10000, |
| 539 | + ) -> JiraFieldContextsResponse: |
| 540 | + """ |
| 541 | + Get contexts for a custom field. |
| 542 | +
|
| 543 | + Args: |
| 544 | + field_id: The ID of the field (e.g., 'customfield_10001') |
| 545 | + start_at: Starting index for pagination (default: 0) |
| 546 | + max_results: Maximum number of results per page (default: 10000) |
| 547 | +
|
| 548 | + Returns: |
| 549 | + JiraFieldContextsResponse with contexts for the field |
| 550 | +
|
| 551 | + Raises: |
| 552 | + ValueError: If the field_id is not provided or invalid |
| 553 | + """ |
| 554 | + if not field_id: |
| 555 | + raise ValueError("Field ID is required") |
| 556 | + |
| 557 | + if not field_id.startswith("customfield_"): |
| 558 | + raise ValueError( |
| 559 | + "Field ID must be a custom field (starting with 'customfield_')" |
| 560 | + ) |
| 561 | + |
| 562 | + try: |
| 563 | + logger.debug(f"Getting contexts for field '{field_id}'") |
| 564 | + |
| 565 | + # Use different API endpoints for Cloud vs DC/Server |
| 566 | + if self.config.is_cloud: |
| 567 | + # Cloud API |
| 568 | + path = f"/rest/api/3/field/{field_id}/context" |
| 569 | + else: |
| 570 | + # DC/Server API - contexts endpoint may not exist or be different |
| 571 | + # For DC, we'll use the same endpoint as Cloud but with API v2 |
| 572 | + path = f"/rest/api/2/field/{field_id}/context" |
| 573 | + |
| 574 | + params = { |
| 575 | + "startAt": start_at, |
| 576 | + "maxResults": max_results, |
| 577 | + } |
| 578 | + |
| 579 | + result = self.jira.get( |
| 580 | + path=path, |
| 581 | + params=params, |
| 582 | + ) |
| 583 | + |
| 584 | + if not isinstance(result, dict): |
| 585 | + error_msg = ( |
| 586 | + f"Unexpected response type from field contexts API: {type(result)}" |
| 587 | + ) |
| 588 | + logger.error(error_msg) |
| 589 | + raise ValueError(error_msg) |
| 590 | + |
| 591 | + # Parse the response using our model |
| 592 | + contexts_response = JiraFieldContextsResponse.from_api_response( |
| 593 | + result, max_results=max_results |
| 594 | + ) |
| 595 | + logger.debug( |
| 596 | + f"Retrieved {len(contexts_response.values)} contexts for field '{field_id}'" |
| 597 | + ) |
| 598 | + return contexts_response |
| 599 | + |
| 600 | + except Exception as e: |
| 601 | + logger.error(f"Error getting contexts for field '{field_id}': {str(e)}") |
| 602 | + raise |
| 603 | + |
| 604 | + def get_field_options( |
| 605 | + self, |
| 606 | + field_id: str, |
| 607 | + start_at: int = 0, |
| 608 | + max_results: int = 10000, |
| 609 | + ) -> JiraFieldOptionsResponse: |
| 610 | + """ |
| 611 | + Get options for a custom field (global options). |
| 612 | +
|
| 613 | + Args: |
| 614 | + field_id: The ID of the field (e.g., 'customfield_10001') |
| 615 | + start_at: Starting index for pagination (default: 0) |
| 616 | + max_results: Maximum number of results per page (default: 10000) |
| 617 | +
|
| 618 | + Returns: |
| 619 | + JiraFieldOptionsResponse with options for the field |
| 620 | +
|
| 621 | + Raises: |
| 622 | + ValueError: If the field_id is not provided or invalid |
| 623 | + """ |
| 624 | + if not field_id: |
| 625 | + raise ValueError("Field ID is required") |
| 626 | + |
| 627 | + if not field_id.startswith("customfield_"): |
| 628 | + raise ValueError( |
| 629 | + "Field ID must be a custom field (starting with 'customfield_')" |
| 630 | + ) |
| 631 | + |
| 632 | + try: |
| 633 | + logger.debug(f"Getting global options for field '{field_id}'") |
| 634 | + |
| 635 | + # Use different API endpoints for Cloud vs DC/Server |
| 636 | + if self.config.is_cloud: |
| 637 | + # Cloud API - different endpoint structure |
| 638 | + path = f"/rest/api/3/field/{field_id}/option" |
| 639 | + else: |
| 640 | + # DC/Server API - uses customFields endpoint with numerical ID only |
| 641 | + # Extract numerical ID from customfield_XXXXX |
| 642 | + numerical_id = field_id.replace("customfield_", "") |
| 643 | + path = f"/rest/api/2/customFields/{numerical_id}/options" |
| 644 | + |
| 645 | + params = { |
| 646 | + "startAt": start_at, |
| 647 | + "maxResults": max_results, |
| 648 | + } |
| 649 | + |
| 650 | + result = self.jira.get( |
| 651 | + path=path, |
| 652 | + params=params, |
| 653 | + ) |
| 654 | + |
| 655 | + if not isinstance(result, dict): |
| 656 | + error_msg = ( |
| 657 | + f"Unexpected response type from field options API: {type(result)}" |
| 658 | + ) |
| 659 | + logger.error(error_msg) |
| 660 | + raise ValueError(error_msg) |
| 661 | + |
| 662 | + # Parse the response using our model |
| 663 | + options_response = JiraFieldOptionsResponse.from_api_response( |
| 664 | + result, max_results=max_results |
| 665 | + ) |
| 666 | + logger.debug( |
| 667 | + f"Retrieved {len(options_response.values)} options for field '{field_id}'" |
| 668 | + ) |
| 669 | + return options_response |
| 670 | + |
| 671 | + except Exception as e: |
| 672 | + logger.error(f"Error getting options for field '{field_id}': {str(e)}") |
| 673 | + raise |
| 674 | + |
| 675 | + def get_field_context_options( |
| 676 | + self, |
| 677 | + field_id: str, |
| 678 | + context_id: str, |
| 679 | + start_at: int = 0, |
| 680 | + max_results: int = 10000, |
| 681 | + ) -> JiraFieldContextOptionsResponse: |
| 682 | + """ |
| 683 | + Get options for a custom field within a specific context. |
| 684 | + This is the most precise way to get field options as they can differ by context. |
| 685 | +
|
| 686 | + Args: |
| 687 | + field_id: The ID of the field (e.g., 'customfield_10001') |
| 688 | + context_id: The ID of the context |
| 689 | + start_at: Starting index for pagination (default: 0) |
| 690 | + max_results: Maximum number of results per page (default: 10000) |
| 691 | +
|
| 692 | + Returns: |
| 693 | + JiraFieldContextOptionsResponse with options for the field in the specified context |
| 694 | +
|
| 695 | + Raises: |
| 696 | + ValueError: If the field_id or context_id is not provided or invalid |
| 697 | + """ |
| 698 | + if not field_id: |
| 699 | + raise ValueError("Field ID is required") |
| 700 | + if not context_id: |
| 701 | + raise ValueError("Context ID is required") |
| 702 | + |
| 703 | + if not field_id.startswith("customfield_"): |
| 704 | + raise ValueError( |
| 705 | + "Field ID must be a custom field (starting with 'customfield_')" |
| 706 | + ) |
| 707 | + |
| 708 | + try: |
| 709 | + logger.debug( |
| 710 | + f"Getting context options for field '{field_id}' in context '{context_id}'" |
| 711 | + ) |
| 712 | + |
| 713 | + # Use different API endpoints for Cloud vs DC/Server |
| 714 | + if self.config.is_cloud: |
| 715 | + # Cloud API |
| 716 | + path = f"/rest/api/3/field/{field_id}/context/{context_id}/option" |
| 717 | + else: |
| 718 | + # DC/Server API - context-specific options may not be available |
| 719 | + # Fall back to general options endpoint with numerical ID only |
| 720 | + # Extract numerical ID from customfield_XXXXX |
| 721 | + numerical_id = field_id.replace("customfield_", "") |
| 722 | + path = f"/rest/api/2/customFields/{numerical_id}/options" |
| 723 | + logger.warning( |
| 724 | + f"DC/Server may not support context-specific options for field '{field_id}', using general options" |
| 725 | + ) |
| 726 | + |
| 727 | + params = { |
| 728 | + "startAt": start_at, |
| 729 | + "maxResults": max_results, |
| 730 | + } |
| 731 | + |
| 732 | + result = self.jira.get( |
| 733 | + path=path, |
| 734 | + params=params, |
| 735 | + ) |
| 736 | + |
| 737 | + if not isinstance(result, dict): |
| 738 | + error_msg = f"Unexpected response type from field context options API: {type(result)}" |
| 739 | + logger.error(error_msg) |
| 740 | + raise ValueError(error_msg) |
| 741 | + |
| 742 | + # Parse the response using our model |
| 743 | + context_options_response = ( |
| 744 | + JiraFieldContextOptionsResponse.from_api_response( |
| 745 | + result, max_results=max_results |
| 746 | + ) |
| 747 | + ) |
| 748 | + logger.debug( |
| 749 | + f"Retrieved {len(context_options_response.values)} options for field '{field_id}' in context '{context_id}'" |
| 750 | + ) |
| 751 | + return context_options_response |
| 752 | + |
| 753 | + except Exception as e: |
| 754 | + logger.error( |
| 755 | + f"Error getting context options for field '{field_id}' in context '{context_id}': {str(e)}" |
| 756 | + ) |
| 757 | + raise |
0 commit comments