Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[discuss] SOAP API: add method for searching within issues #560

Merged
merged 5 commits into from Feb 3, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
90 changes: 90 additions & 0 deletions api/soap/mantisconnect.wsdl
Expand Up @@ -5,6 +5,13 @@
<xsd:schema targetNamespace="http://futureware.biz/mantisconnect">
<xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<xsd:import namespace="http://schemas.xmlsoap.org/wsdl/"/>
<xsd:complexType name="IntegerArray">
<xsd:complexContent>
<xsd:restriction base="SOAP-ENC:Array">
<xsd:attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="xsd:integer[]"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="StringArray">
<xsd:complexContent>
<xsd:restriction base="SOAP-ENC:Array">
Expand Down Expand Up @@ -259,6 +266,43 @@
<xsd:element name="url" type="xsd:string" minOccurs="0"/>
</xsd:all>
</xsd:complexType>
<xsd:complexType name="FilterSearchData">
<xsd:all>
<xsd:element name="project_id" type="tns:IntegerArray" minOccurs="1"/>
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should make fields like project_id, issues_per_page and page_number required. Each one has a sensible default value: project_id should default to 0 (for all projects), issues_per_project to the configuration option that determines number of issues on View Issues page, page number to first page (I assume we are 1-based).

<xsd:element name="search" type="xsd:string" minOccurs="0"/>
Copy link
Member

Choose a reason for hiding this comment

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

Do we want a more self-explanatory term than search? E.g. text_search, full_text_search or similar

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the web mantisbt filter UI is it named as search so i used the same name.

<xsd:element name="category" type="tns:StringArray" minOccurs="0"/>
Copy link
Member

Choose a reason for hiding this comment

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

category_name would be clearer IMO

<xsd:element name="severity_id" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="status_id" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="priority_id" type="tns:StringArray" minOccurs="0"/>
Copy link
Member

Choose a reason for hiding this comment

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

Why is priority_id a StringArray?

<xsd:element name="reporter_id" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="handler_id" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="note_user_id" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="resolution_id" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="product_version" type="tns:StringArray" minOccurs="0"/>
<xsd:element name="user_monitor_id" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="hide_status_id" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="sort" type="xsd:string" minOccurs="0"/>
Copy link
Member

Choose a reason for hiding this comment

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

sort_field ?

<xsd:element name="sort_direction" type="xsd:string" minOccurs="0"/>
<xsd:element name="sticky" type="xsd:boolean" minOccurs="0"/>
<xsd:element name="view_state_id" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="fixed_in_version" type="tns:StringArray" minOccurs="0"/>
<xsd:element name="target_version" type="tns:StringArray" minOccurs="0"/>
<xsd:element name="platform" type="tns:StringArray" minOccurs="0"/>
<xsd:element name="os" type="tns:StringArray" minOccurs="0"/>
<xsd:element name="os_build" type="tns:StringArray" minOccurs="0"/>
<xsd:element name="start_day" type="xsd:integer" minOccurs="0"/>
<xsd:element name="start_month" type="xsd:integer" minOccurs="0"/>
<xsd:element name="start_year" type="xsd:integer" minOccurs="0"/>
<xsd:element name="end_day" type="xsd:integer" minOccurs="0"/>
<xsd:element name="end_month" type="xsd:integer" minOccurs="0"/>
<xsd:element name="end_year" type="xsd:integer" minOccurs="0"/>
<xsd:element name="tag_string" type="tns:StringArray" minOccurs="0"/>
<xsd:element name="tag_select" type="tns:IntegerArray" minOccurs="0"/>
<xsd:element name="custom_fields" type="tns:FilterCustomFieldArray" minOccurs="0"/>
<xsd:element name="page_number" type="xsd:integer" minOccurs="1"/>
Copy link
Member

Choose a reason for hiding this comment

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

Technically speaking the page_number and issues_per_page are not part of the filter definition, but are part of a search request. I wonder if we should have a FilterDefinition and SearchRequest. A FilterSearchData can reference a filter id or a filter definition + page size / number. This makes the filter definition re-usable with other methods to add/get filters for example.

<xsd:element name="issues_per_page" type="xsd:integer" minOccurs="1"/>
</xsd:all>
</xsd:complexType>
<xsd:complexType name="FilterDataArray">
<xsd:complexContent>
<xsd:restriction base="SOAP-ENC:Array">
Expand Down Expand Up @@ -288,6 +332,20 @@
<xsd:element name="require_closed" type="xsd:boolean" minOccurs="0"/>
</xsd:all>
</xsd:complexType>
<xsd:complexType name="FilterCustomField">
<xsd:all>
<xsd:element name="name" type="xsd:string" minOccurs="0"/>
Copy link
Member

Choose a reason for hiding this comment

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

We typically use ObjectRef for cases where a user can supply an id or a name to reference an entity.

<xsd:element name="id" type="xsd:integer" minOccurs="0"/>
<xsd:element name="value" type="tns:StringArray" minOccurs="0"/>
Copy link
Member

Choose a reason for hiding this comment

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

Should we require the value for custom field? Otherwise, why would the record exist?

</xsd:all>
</xsd:complexType>
<xsd:complexType name="FilterCustomFieldArray">
<xsd:complexContent>
<xsd:restriction base="SOAP-ENC:Array">
<xsd:attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="tns:FilterCustomField[]"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="CustomFieldDefinitionDataArray">
<xsd:complexContent>
<xsd:restriction base="SOAP-ENC:Array">
Expand Down Expand Up @@ -747,6 +805,18 @@
<part name="per_page" type="xsd:integer" /></message>
<message name="mc_filter_get_issue_headersResponse">
<part name="return" type="tns:IssueHeaderDataArray" /></message>
<message name="mc_filter_search_issue_headersRequest">
<part name="username" type="xsd:string" />
<part name="password" type="xsd:string" />
<part name="filter" type="tns:FilterSearchData" /></message>
<message name="mc_filter_search_issue_headersResponse">
<part name="return" type="tns:IssueHeaderDataArray" /></message>
<message name="mc_filter_search_issuesRequest">
<part name="username" type="xsd:string" />
<part name="password" type="xsd:string" />
<part name="filter" type="tns:FilterSearchData" /></message>
<message name="mc_filter_search_issuesResponse">
<part name="return" type="tns:IssueDataArray" /></message>
<message name="mc_config_get_stringRequest">
<part name="username" type="xsd:string" />
<part name="password" type="xsd:string" />
Expand Down Expand Up @@ -1094,6 +1164,16 @@
<input message="tns:mc_filter_get_issue_headersRequest"/>
<output message="tns:mc_filter_get_issue_headersResponse"/>
</operation>
<operation name="mc_filter_search_issue_headers">
<documentation>Get the issue headers that match the custom filter and paging details.</documentation>
<input message="tns:mc_filter_search_issue_headersRequest"/>
<output message="tns:mc_filter_search_issue_headersResponse"/>
</operation>
<operation name="mc_filter_search_issues">
<documentation>Get the issues that match the custom filter and paging details.</documentation>
<input message="tns:mc_filter_search_issuesRequest"/>
<output message="tns:mc_filter_search_issuesResponse"/>
</operation>
<operation name="mc_config_get_string">
<documentation>Get the value for the specified configuration variable.</documentation>
<input message="tns:mc_config_get_stringRequest"/>
Expand Down Expand Up @@ -1432,6 +1512,16 @@
<input><soap:body use="encoded" namespace="http://futureware.biz/mantisconnect" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
<output><soap:body use="encoded" namespace="http://futureware.biz/mantisconnect" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
</operation>
<operation name="mc_filter_search_issue_headers">
<soap:operation soapAction="http://www.mantisbt.org/bugs/api/soap/mantisconnect.php/mc_filter_search_issue_headers" style="rpc"/>
<input><soap:body use="encoded" namespace="http://futureware.biz/mantisconnect" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
<output><soap:body use="encoded" namespace="http://futureware.biz/mantisconnect" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
</operation>
<operation name="mc_filter_search_issues">
<soap:operation soapAction="http://www.mantisbt.org/bugs/api/soap/mantisconnect.php/mc_filter_search_issues" style="rpc"/>
<input><soap:body use="encoded" namespace="http://futureware.biz/mantisconnect" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
<output><soap:body use="encoded" namespace="http://futureware.biz/mantisconnect" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
</operation>
<operation name="mc_config_get_string">
<soap:operation soapAction="http://www.mantisbt.org/bugs/api/soap/mantisconnect.php/mc_config_get_string" style="rpc"/>
<input><soap:body use="encoded" namespace="http://futureware.biz/mantisconnect" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
Expand Down
206 changes: 206 additions & 0 deletions api/soap/mc_filter_api.php
Expand Up @@ -23,6 +23,50 @@
* @link http://www.mantisbt.org
*/

# Path to MantisBT is assumed to be the grand parent directory. If this is not
# the case, then this variable should be set to the MantisBT path.
# This can not be a configuration option, then MantisConnect configuration
# needs MantisBT to be included first to make use of the constants and possibly
# configuration defined in MantisBT.

require_api( 'filter_constants_inc.php' );

// doesn't contain 'custom_fields'
$_SOAP_API_TO_FILTER_API_NAMES = array (
Copy link
Member

Choose a reason for hiding this comment

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

Typically we use $g_some_normal_name

'search' => FILTER_PROPERTY_SEARCH,
'category' => FILTER_PROPERTY_CATEGORY_ID,
'severity_id' => FILTER_PROPERTY_SEVERITY,
'status_id' => FILTER_PROPERTY_STATUS,
'priority_id' => FILTER_PROPERTY_PRIORITY,
'reporter_id' => FILTER_PROPERTY_REPORTER_ID,
'handler_id' => FILTER_PROPERTY_HANDLER_ID,
'project_id' => FILTER_PROPERTY_PROJECT_ID,
'note_user_id' => FILTER_PROPERTY_NOTE_USER_ID,
'resolution_id' => FILTER_PROPERTY_RESOLUTION,
'version' => FILTER_PROPERTY_VERSION,

'user_monitor_id' => FILTER_PROPERTY_MONITOR_USER_ID,
'hide_status_id' => FILTER_PROPERTY_HIDE_STATUS,
'sort' => FILTER_PROPERTY_SORT_FIELD_NAME,
'sort_direction' => FILTER_PROPERTY_SORT_DIRECTION,
'sticky' => FILTER_PROPERTY_STICKY,
'view_state' => FILTER_PROPERTY_VIEW_STATE,
'fixed_in_version' => FILTER_PROPERTY_FIXED_IN_VERSION,
'target_version' => FILTER_PROPERTY_TARGET_VERSION,
'platform' => FILTER_PROPERTY_PLATFORM,
'os' => FILTER_PROPERTY_OS,
'os_build' => FILTER_PROPERTY_OS_BUILD,
'start_day' => FILTER_PROPERTY_START_DAY,
'start_month' => FILTER_PROPERTY_START_MONTH,
'start_year' => FILTER_PROPERTY_START_YEAR,
'end_day' => FILTER_PROPERTY_END_DAY,
'end_month' => FILTER_PROPERTY_END_MONTH,
'end_year' => FILTER_PROPERTY_END_YEAR,
'tag_string' => FILTER_PROPERTY_TAG_STRING,
'tag_select' => FILTER_PROPERTY_TAG_SELECT,
);


/**
* Get all user defined issue filters for the given project.
*
Expand Down Expand Up @@ -147,3 +191,165 @@ function mc_filter_get_issue_headers( $p_username, $p_password, $p_project_id, $

return $t_result;
}

/**
* Get all issue headers matching the custom filter.
*
* @param string $p_username The name of the user trying to access the filters.
* @param string $p_password The password of the user.
* @param FilterSearchData $p_filter_search The custom filter.
* @return array that represents an IssueHeaderDataArray structure
*/
function mc_filter_search_issue_headers( $p_username, $p_password, $p_filter_search ) {

global $_SOAP_API_TO_FILTER_API_NAMES;

$t_user_id = mci_check_login( $p_username, $p_password );

if( $t_user_id === false ) {
return mci_soap_fault_login_failed();
}

// object to array
if( is_object($p_filter_search) ) {
$p_filter_search = get_object_vars($p_filter_search);
}

$t_project_id = $p_filter_search['project_id'];

if( !mci_has_readonly_access( $t_user_id, $t_project_id ) ) {
Copy link
Member

Choose a reason for hiding this comment

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

We are checking access against a single project here. We are unlikely to disclose projects, but it is likely that APIs will fail with some html that will break soap. We should run this validation on all the project ids supplied and fail if the list contains any non-accessible projects. Another option is to filter the project_ids based on access and just pass to the filters the ones that the user has access to.

return mci_soap_fault_access_denied( $t_user_id );
}

$t_filter = array( '_view_type' => 'advanced' );

// default fields
foreach( $_SOAP_API_TO_FILTER_API_NAMES as $t_soap_name => $t_filter_name ) {
if ( isset ( $p_filter_search[$t_soap_name]) ) {

$t_value = $p_filter_search[$t_soap_name];
error_log('Simple extraction, value is ' . print_r($t_value, true) . ' .' );
Copy link
Member

Choose a reason for hiding this comment

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

I would drop these error_logs in favor of log_event() calls (see core/logging_api.php). This directs the log to MantisBT log files and have the log entries visibility controlled by $g_logging_level (see config_defaults_inc.php) having LOG_FILTERING set.

$t_filter[$t_filter_name] = $t_value;
}
}

// custom fields
if( isset ( $p_filter_search['custom_fields']) ) {
foreach( $p_filter_search['custom_fields'] as $t_custom_field ) {

// object to array
if( is_object($t_custom_field) ) {
$t_custom_field = get_object_vars($t_custom_field);
}

// if is set custom_field's id, use it primary
if ( isset($t_custom_field['id']) ) {
$t_custom_field_id = $t_custom_field['id'];
}
else {
$t_custom_field_id = custom_field_get_id_from_name( $t_custom_field['name'] );
}

$t_value = $t_custom_field['value'];
error_log('custom field id ' . $t_custom_field_id . print_r($t_value, true) . ' .' );
$t_filter['custom_fields'][$t_custom_field_id] = $t_value;
}
}

$t_filter = filter_ensure_valid_filter( $t_filter );

$t_result = array();
$t_page_number = 0;
$t_per_page = $p_filter_search['issues_per_page'];
$t_page_count = 0;
$t_bug_count = 0;
$t_rows = filter_get_bug_rows( $t_page_number, $t_per_page, $t_page_count, $t_bug_count, $t_filter );

foreach( $t_rows as $t_issue_data ) {
$t_result[] = mci_issue_data_as_header_array( $t_issue_data );
}

return $t_result;
}

/**
* Get all issues matching the custom filter.
*
* @param string $p_username The name of the user trying to access the filters.
* @param string $p_password The password of the user.
* @param FilterSearchData $p_filter_search The custom filter.
* @return array that represents an IssueDataArray structure
*/
function mc_filter_search_issues( $p_username, $p_password, $p_filter_search ) {
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we have too much code duplication between the two methods. How about the following:

  • Adding mc_issue_get_many( array(ids) ) that returns an array of the same element type as mc_issue_get().
  • Using the output of the issue_search_headers() to provide the input for mc_issue_get_many().
  • Is we end up with too much duplicate code, then have some helper method that is called internally from multiple places.

Suggests for better name for mc_issue_get_many() is certainly welcome!

In some cases, this may mean an extra round trip, but it is useful to have our methods usable in more than one scenario. We can even go to the model where we just return ids from the search and then have methods than return array of headers or array of full issues.


global $_SOAP_API_TO_FILTER_API_NAMES;

$t_user_id = mci_check_login( $p_username, $p_password );

if( $t_user_id === false ) {
return mci_soap_fault_login_failed();
}

$t_lang = mci_get_user_lang( $t_user_id );

// object to array
if( is_object($p_filter_search) ) {
$p_filter_search = get_object_vars($p_filter_search);
}

$t_project_id = $p_filter_search['project_id'];

if( !mci_has_readonly_access( $t_user_id, $t_project_id ) ) {
return mci_soap_fault_access_denied( $t_user_id );
}

$t_filter = array( '_view_type' => 'advanced' );

// default fields
foreach( $_SOAP_API_TO_FILTER_API_NAMES as $t_soap_name => $t_filter_name ) {
if ( isset ( $p_filter_search[$t_soap_name]) ) {

$t_value = $p_filter_search[$t_soap_name];
error_log('Simple extraction, value is ' . print_r($t_value, true) . ' .' );
$t_filter[$t_filter_name] = $t_value;
}
}

// custom fields
if( isset ( $p_filter_search['custom_fields']) ) {
foreach( $p_filter_search['custom_fields'] as $t_custom_field ) {

// object to array
if( is_object($t_custom_field) ) {
$t_custom_field = get_object_vars($t_custom_field);
}

// if is set custom_field's id, use it primary
if ( isset($t_custom_field['id']) ) {
$t_custom_field_id = $t_custom_field['id'];
}
else {
$t_custom_field_id = custom_field_get_id_from_name( $t_custom_field['name'] );
}

$t_value = $t_custom_field['value'];
error_log('custom field id ' . $t_custom_field_id . print_r($t_value, true) . ' .' );
$t_filter['custom_fields'][$t_custom_field_id] = $t_value;
}
}

$t_filter = filter_ensure_valid_filter( $t_filter );

$t_result = array();
$t_page_number = 0;
$t_per_page = $p_filter_search['issues_per_page'];
$t_page_count = 0;
$t_bug_count = 0;
$t_rows = filter_get_bug_rows( $t_page_number, $t_per_page, $t_page_count, $t_bug_count, $t_filter );

foreach( $t_rows as $t_issue_data ) {
$t_result[] = mci_issue_data_as_array( $t_issue_data, $t_user_id, $t_lang );
}

return $t_result;
}