Analytics Search Manager (.NET)

Relativity analytics search enables semantic analysis of large data sets. For more information, see Analytics and Saved Search on the RelativityOne Documentation site.

The Services API supports create, read, update, delete, and query operations on an AnalyticsSearch DTO. It also provides helper methods to easily return saved search parameters that are available to the user in the workspace, such as fields, search owners, search indexes. You can also generate email links to search results. The operations on KeywordSearch DTOs are performed asynchronously. Note that asynchronous operations return a standard .NET System.Threading.Tasks.Task<TResult> object that can be used to monitor progress and interact with the process.

You can programmatically execute analytics search using the SavedSearchCondition class of the Relativity Services API or saved search query in the REST API.

You can also perform create, read, update, delete, and query operations on the AnalyticsSearch objects using the Relativity REST API.

This page contains the following information:

AnalyticsSearch properties

The following AnalyticsSearch object properties correspond to the analytics search properties that can be specified through the Relativity user interface (shown in the images below). For code sample of setting the properties, see Creating an AnalyticsSearch and AnalyticsSearch helper methods.

  • ArtifactID - ArtifactID of the saved search object.
  • ArtifactTypeID - Artifact Type ID of the object to be returned by the search. Currently only Document is supported. Use ArtifactType enumeration to set the value. This is a required property when creating and updating saved searches.
  • Name - descriptive name of the saved search. This is a required property when creating and updating saved searches.
  • Includes - the field for identifying documents related to the documents matching the search criteria. The related documents will be included in the result set alongside with the documents that match the search criteria. Use the GetSearchIncludesAsync helper method to return the property value.
  • Scope - The scope of the search (entire repository or selected folders) specified as a ScopeType enumeration.

    ScopeType enumeration values

    Member nameValue Description
    EntireCase0Search the entire workspace.
    Folders1Search folders.
    Subfolders2Search subfolders
  • SearchFolders - if folders or subfolders are specified as the Scope value, the folders to be included in the search.
  • RequiresManualRun - requires users to rerun a saved search when they return to it to ensure up-to-date search results.

    Saved search basic information

  • Dashboard - the dashboard associated with the saved search.
  • SearchIndex – the analytics index used by the search.
  • ConceptsText - concept text to be analyzed by the search. For more information, see Concept searching on the Relativity 9.6 Documentation site.
  • MinimumConceptRank – the minimum qualifying score for documents to be included in the results. The score is a value between 0 and 1, for example, 0.6. 1 indicates that the documents are conceptually identical.
  • SortByRank – when searching for concepts, indicates that the search results must be sort by concept rank.

    Rank sorting

  • KeywordsText – the terms for keyword expansion. Keyword expansion returns a list of highly correlated terms, synonyms, or strongly related terms in your document set.
  • FuzzinessLevel – the fuzziness level for search term matching.
  • SearchCriteria - search conditions specified as a CriteriaCollection object. For more information, see Saved search conditions criteria.

    Saved search conditions

  • Fields - the fields to be included in the search result set specified as a collection of Field objects. Use the GetFieldsForSearchResultViewAsync helper method to return the property value. This is a required property when creating and updating saved searches.

    Saved search fields

  • Sorts - sort order for search results specified as a Sorts object.

    Saved search sorts

  • Keywords - an optional field where extra group information may be recorded.
  • Notes - detailed description of the saved search.
  • QueryHint - string parameter used to optimize views. Only use the query hint if instructed by the kCura Client Services team. Currently, you can use Hashjoin:(true/false) or Maxdrop:(x) to populate the field.
  • RelativityApplications - Relativity applications that use the saved search.

    Saved search miscellaneous information

  • Owner - user(s) who can access the saved search. Setting the value to 0 enables all users with permissions to the saved search are able to see it. Use the GetSearchOwnersAsync helper method to return the property value.

    Saved search owner

  • SearchContainer - the saved search folder. If no value is specified, the search will be saved at the logical root of the saved search view.
  • SystemCreatedBy - ArtifactID of the user who created the search.
  • SystemCreatedOn - date and time in UTC when the search was created.
  • SystemLastModifiedBy - ArtifactID of the user who last modified the search.
  • SystemLastModifiedOn - date and time in UTC when the search was last modified.

Create an AnalyticsSearch

You can create analytics searches asynchronously using the CreateSingleAsync() method of the IAnalyticsSearchManager interface.

The following code sample illustrates how to create an AnalyticsSearch. Note how the GetFieldsForSearchResultView() method is used to return all available workspace fields as the resultFields.FieldsNotIncluded property. This is accomplished by specifying the workspace ID and 0 as the AnalyticsSearch ArtifactID as GetFieldsForSearchResultView() parameters. For more information, see AnalyticsSearch helper methods. Subsequently the Edit, File Icon, Doc ID Beg, Email Subject, and Artifact ID are included as the search result fields. The results are sorted by ArtifactID (ascending). CreateAsync() returns System.Threading.Tasks.Task<TResult>.

public async Task<bool> CreateSingleAsync_AnalyticsSearch(Client.SamplesLibrary.Helper.IHelper helper)
{
    bool success = false;

    //Get a connection to the API using the API helper classes, available in Event Handlers,
    //Agents, and Custom Pages. They are mocked here for samples purposes
    //NOTE: We are executing under the context of the current user. For more info, see the APIHelper documentation
    using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
    {
        try
        {
            AnalyticsSearch search = new AnalyticsSearch();
            search.Name = "My AnalyticsSearch";

            // Get all the query fields available to the current user.
            SearchResultViewFields resultFields = await proxy.GetFieldsForSearchResultViewAsync(this.SampleWorkspace_ID, (int)ArtifactType.Document);
            Services.Field.FieldRef field;

            // Get a Analytics SearchIndex and set it.
            List<SearchIndexRef> searchIndexes = await proxy.GetSearchIndexesAsync(this.SampleWorkspace_ID);
            search.SearchIndex = searchIndexes.FirstOrDefault();

            // Set the owner to the current user, in this case "Admin, Relativity," or "0" for public.
            List<Services.User.UserRef> searchOwners = await proxy.GetSearchOwnersAsync(this.SampleWorkspace_ID);
            search.Owner = searchOwners.First(o => o.Name == "Admin, Relativity");

            // Add the fields to the Fields collection.
            // If a field Name, ArtifactID, Guid, or ViewFieldID is known, a field can be set with that information as well.
            field = resultFields.FieldsNotIncluded.First(f => f.Name == "Edit");
            search.Fields.Add(field);
            field = resultFields.FieldsNotIncluded.First(f => f.Name == "File Icon");
            search.Fields.Add(field);
            field = resultFields.FieldsNotIncluded.First(f => f.Name == "Doc ID Beg");
            search.Fields.Add(field);
            field = resultFields.FieldsNotIncluded.First(f => f.Name == "Email Subject");
            search.Fields.Add(field);

            // Create a Criteria for the field named "Email Subject" where the value is "FW: "
            // NOTE: We assume a field named "Email Subject" exists therefore it can be set by only the name.
            // See aditional examples below in the region "Condition Examples."
            Criteria criteria = new Criteria();
            criteria.Condition = new CriteriaCondition(new Services.Field.FieldRef()
            {
                Name = "Email Subject"
            }, CriteriaConditionEnum.IsLike, "FW: ");

            // Add the search condition criteria to the collection.
            search.SearchCriteria.Conditions.Add(criteria);

            // Sort this search based on the ArtifactID Ascending, and Date Sent Descending.
            field = resultFields.FieldsNotIncluded.First(f => f.Name == "Artifact ID");
            Services.Sort sort = new Services.Sort()
            {
                FieldIdentifier = new Services.Field.FieldRef() { ArtifactID = field.ArtifactID },
                Direction = SortEnum.Ascending,
                Order = 1
            };
            search.Sorts.Add(sort);

            field = resultFields.FieldsNotIncluded.First(f => f.Name == "Date Sent");
            sort = new Services.Sort()
            {
                FieldIdentifier = new Services.Field.FieldRef() { ArtifactID = field.ArtifactID },
                Direction = SortEnum.Descending,
                Order = 2
            };
            search.Sorts.Add(sort);

            // Search for the concept string "John is cool" with a minimum comcept rank of 60 and sort by rank.
            search.ConceptsText = "John is cool";
            search.MinimumConceptRank = "60";
            search.SortByRank = true;

            // Add a note.
            search.Notes = "This is my new AnalyticsSearch";

            // Create the search.
            int artifactID = await proxy.CreateSingleAsync(this.SampleWorkspace_ID, search);

            if (artifactID != 0)
            {
                success = true;
            }
            else
            {
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, "Create failed", this.GetType().Name);
            }
        }
        catch (ServiceException exception)
        {
            //Exceptions are returned as an ServiceException
            this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, exception.Message, this.GetType().Name);
        }

    }

    return success;
}

To associate an analytics search with a Relativity dashboard, use the Dashboard property of the search object:

//Associate with dashboard.
search.Dashboard = new Services.DashboardObject.DashboardRef(this.SampleDashboardArtifact_ID);

Read an AnalyticsSearch

The following code sample illustrates how to read an AnalyticsSearch DTO using the ReadSingleAsync() method of the IAnalyticsSearchManager interface..

public async Task<bool> ReadSingleAsync_dtSearch(Client.SamplesLibrary.Helper.IHelper helper)
{
    bool success = false;

    //Get a connection to the API using the API helper classes, available in Event Handlers,
    //Agents, and Custom Pages. They are mocked here for samples purposes
    //NOTE: We are executing under the context of the current user. For more info, see the APIHelper documentation
    using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
    {
        try
        {
            // Create sample data.
            int? savedSearchID;
            if (Client.SamplesLibrary.Data.SavedSearchHelper.TryCreate(proxy, this.Logger, this.SampleWorkspace_ID,
                        "My AnalyticsSearch For Read", out savedSearchID))
            {
                // Read the search artifact.
                AnalyticsSearch search = await proxy.ReadSingleAsync(this.SampleWorkspace_ID, (int)savedSearchID);

                // Display the search artifact result.
                string info = string.Format("{0} - {1}", search.ArtifactID, search.Name);
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, info,
                this.GetType().Name);

                success = true;
            }
        }
        catch (ServiceException exception)
        {
            //Exceptions are returned as an ServiceException
            this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name,
            exception.Message, this.GetType().Name);
        }

        return success;
    }
}

Update an AnalyticsSearch

The following code sample illustrates how to update an analytics search using the UpdateSingleAsync() method of the IAnalyticsSearchManager interface.

public async Task<bool> UpdateSingleAsync_AnalyticsSearch(Client.SamplesLibrary.Helper.IHelper helper)
{
    bool success = false;

    //Get a connection to the API using the API helper classes, available in Event Handlers,
    //Agents, and Custom Pages. They are mocked here for samples purposes
    //NOTE: We are executing under the context of the current user. For more info, see the APIHelper documentation
    using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
    {
        int? savedSearchID;
        if (Client.SamplesLibrary.Data.SavedSearchHelper.TryCreate(proxy, this.Logger, this.SampleWorkspace_ID, "My AnalyticsSearch", out savedSearchID))
        {
            try
            {
                // Read the search artifact.
                AnalyticsSearch search = await proxy.ReadSingleAsync(this.SampleWorkspace_ID, (int)savedSearchID);

                // Modify the search name.
                search.Name = "My AnalyticsSearch Updated";

                // Add a search criteria to the search.
                search.SearchCriteria.Conditions.Add(new Criteria()
                {
                    Condition = new CriteriaCondition(new Services.Field.FieldRef("Email Subject"),
                    CriteriaConditionEnum.IsLike,
                    "Important")
                });

                // Add a result field to the search.
                search.Fields.Add(new Services.Field.FieldRef("Date Sent"));

                // Update the search.
                await proxy.UpdateSingleAsync(this.SampleWorkspace_ID, search);
                success = true;
            }
            catch (ServiceException exception)
            {
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, exception.Message, this.GetType().Name);
            }
        }
    }

    return success;
}

Delete an AnalyticsSearch

You can delete analytics searches asynchronously by using the DeleteSingleAsync() method of the IAnalyticsSearchManager interface.

The following code sample illustrates how to delete an AnalyticsSearch.

public async Task<bool> DeleteSingleAsync_AnalyticsSearch(Client.SamplesLibrary.Helper.IHelper helper)
{
    bool success = false;

    //Get a connection to the API using the API helper classes, available in Event Handlers,
    //Agents, and Custom Pages. They are mocked here for samples purposes
    //NOTE: We are executing under the context of the current user. For more info, see the APIHelper documentation
    using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
    {
        int? savedSearchID;
        if (Client.SamplesLibrary.Data.SavedSearchHelper.TryCreate(proxy, this.Logger, this.SampleWorkspace_ID, "My AnalyticsSearch", out savedSearchID))
        {
            try
            {
                await proxy.DeleteSingleAsync(this.SampleWorkspace_ID, (int)savedSearchID);
                success = true;
            }
            catch (ServiceException exception)
            {
                //Exceptions are returned as an ServiceException
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, exception.Message, this.GetType().Name);
            }
        }
        else
        {
            this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, "Delete failed", this.GetType().Name);
        }
    }

    return success;
}

Query AnalyticsSearches

The following code sample illustrates how to query AnalyticsSearch DTOs using the QueryAsync() and QuerySubsetAsync() methods of the IAnalyticsSearchManager interface. The query condition checks if the search name starts with the string "My". If the query returns a token value that is not null, more results are available than initially specified in the length property, and they are subsequently retrieved by using the QuerySubsetAsync() method. When the length parameter is not specified, its value defaults to 0, and the number of returned results defaults to the instance setting value of PDVDefaultQueryCacheSize of 10000. For more information about query conditions and using query tokens, see Search Relativity.

public async Task<bool> QueryAsync_AnalyticsSearch(Client.SamplesLibrary.Helper.IHelper helper)
{
    bool success = false;

    //Get a connection to the API using the API helper classes, available in Event Handlers,
    //Agents, and Custom Pages. They are mocked here for samples purposes
    //NOTE: We are executing under the context of the current user. For more info, see the APIHelper documentation
    using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
    {
        try
        {
            // Create sample data.
            int? savedSearchID;
            for (int i = 0; i < 12; i++)
            {
                Client.SamplesLibrary.Data.SavedSearchHelper.TryCreate(proxy, this.Logger, this.SampleWorkspace_ID, string.Format("My AnalyticsSearch For Query {0}", i), out savedSearchID);
            }

            // Create a new instance of a Query.
            Services.Query query = new Services.Query();

            // Define the search length. This is the number of results to be returned.
            // If more results are available the search results will contain a query key that can be used with QuerySubsetAsync
            // to get the additional results from the search query. Setting length to 0 will use the default length defined in Relativity.
            int length = 5;

            // Define the search condition for the query.  Conditions can be created programmatically using Conditions and converted
            // to a query string using the ToQueryString extension method that the query conditions accepts.
            Condition queryCondition = new TextCondition("Name", TextConditionEnum.StartsWith, "My");
            string queryString = queryCondition.ToQueryString();
            query.Condition = queryString;

            // Create an instance of a Sort and define how this query is to be sorted.
            Services.Sort sortBy = new Services.Sort();
            sortBy.FieldIdentifier.Name = "ArtifactID";
            sortBy.Order = 0;
            sortBy.Direction = SortEnum.Descending;
            query.Sorts.Add(sortBy);

            // Query for search objects given the above query condition and sort order.
            AnalyticsSearchQueryResultSet queryResultSet = await proxy.QueryAsync(this.SampleWorkspace_ID, query, length);

            // Check to see if the query was successful.
            if (queryResultSet.Success)
            {
                // Loop through the search results and display successful search results.
                foreach (Services.Result<AnalyticsSearch> result in queryResultSet.Results)
                {
                    // If the result was successful display the ArtifactID and Name, if it is not display the error message.
                    if (result.Success)
                    {
                        string info = string.Format("{0} - {1}", result.Artifact.ArtifactID, result.Artifact.Name);
                        this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, info, this.GetType().Name);
                    }
                    else
                    {
                        string info = string.Format("Error: {0}", result.Message);
                        this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, info, this.GetType().Name);
                    }
                }

                // If a QueryToken exists more results are available.
                int queryStartPosition = 1 + length;
                while (!string.IsNullOrEmpty(queryResultSet.QueryToken))
                {
                    // Query for the subset of query results.
                    queryResultSet = await proxy.QuerySubsetAsync(this.SampleWorkspace_ID, queryResultSet.QueryToken, queryStartPosition, length);

                    // Repeat the same process to read results as seen in QueryAsync.
                    // Check to see if the query was successful.
                    if (queryResultSet.Success)
                    {
                        // Loop through the search results and display successful search results.
                        foreach (Services.Result<AnalyticsSearch> result in queryResultSet.Results)
                        {
                            // If the result was successful display the ArtifactID and Name, if it is not display the error message.
                            if (result.Success)
                            {
                                string info = string.Format("{0} - {1}", result.Artifact.ArtifactID, result.Artifact.Name);
                                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, info, this.GetType().Name);
                            }
                            else
                            {
                                string info = string.Format("Error: {0}", result.Message);
                                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, info, this.GetType().Name);
                            }
                        }

                        // Shift the starting position.
                        queryStartPosition += length;
                    }
                    else
                    {
                        string info = string.Format("Error: QuerySubsetAsync was not successfull - {0}", queryResultSet.Message);
                        this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, info, this.GetType().Name);
                    }
                }
            }
            else
            {
                string info = string.Format("Error: QueryAsync was not successfull - {0}", queryResultSet.Message);
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, info, this.GetType().Name);
            }
        }
        catch (ServiceException exception)
        {
            //Exceptions are returned as an ServiceException
            this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, exception.Message, this.GetType().Name);
        }
    }

    return success;
}

Helper methods

IAnalyticsSearchManager interface provides the following asynchronous helper methods to return workspace parameters for populating analytics saved search properties:

  • GetSearchIndexesAsync – returns all analytics indexes available to the user in the workspace. Use to populate the AnalyticsSearch.SearchIndex property.
  • GetFieldsForSearchResultViewAsync - returns the SearchResultViewFields object. If an ArtifactID of an existing search is specified as an input parameter, the fields included in the saved search are returned as the SearchResultViewFields.FieldsIncluded property. If no search Artifact ID is specified, then all workspace fields available to the user are returned as SearchResultViewFields.FieldsNotIncluded property. Use to populate the Fields property.
  • GetSearchIncludesAsync - returns all relational fields available to the user in the workspace. Use to populate the Includes property.
  • GetSearchOwnersAsync - returns all users in the workspace with permissions to view saved searches. Use to populate the Owners property.
  • GetFieldsForCriteriaConditionAsync - returns all workspace fields available to the user that can be included in a saved search condition as a list of Field objects.
  • GetFieldsForObjectCriteriaCollectionAsync - returns all workspace fields available to the user that can be specified as a subcondition for a given field in a saved search condition. Use to populate field values for batch and multi-object conditions. For more information, see Saved search conditions criteria.
  • GetAccessStatusAsync - returns the SearchAccessStatus object with the information about the user‘s ability to access the saved search. The fields include:
    • Exists – indicates whether the search exists relative to the specified folder path.
    • CanView – indicates whether the user has view permissions to the search.
    • CanAccessSearchProvider – indicates whether the user has view permissions to the search provider used in the saved search.
    • CanViewCriteriaFields – indicates whether the user has view permissions to all of the fields used in the saved search.

The following example illustrates how to use the helper methods to return the fields, indexes, search owners, and relational fields available in the workspace.

public async Task<bool> HelpersAsync_AnalyticsSearch(Client.SamplesLibrary.Helper.IHelper helper)
{
    bool success = false;

    //Get a connection to the API using the API helper classes, available in Event Handlers,
    //Agents, and Custom Pages. They are mocked here for samples purposes
    //NOTE: We are executing under the context of the current user. For more info, see the APIHelper documentation
    using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
    {
        try
        {
            List<Services.User.UserRef> searchOwners = await proxy.GetSearchOwnersAsync(this.SampleWorkspace_ID);

            List<Services.Field.FieldRef> searchIncludes = await proxy.GetSearchIncludesAsync(this.SampleWorkspace_ID);

            // All available fields will be returned in the FieldsNotIncluded collection.
            SearchResultViewFields resultFields = await proxy.GetFieldsForSearchResultViewAsync(this.SampleWorkspace_ID, (int)ArtifactType.Document);

            List<SearchIndexRef> searchIndexes = await proxy.GetSearchIndexesAsync(this.SampleWorkspace_ID);

            List<Services.Field.FieldRef> searchConditionFields = await proxy.GetFieldsForCriteriaConditionAsync(this.SampleWorkspace_ID, (int)ArtifactType.Document);

            foreach (Services.User.UserRef owner in searchOwners)
            {
                string ownerString = string.Format("Owner: {0} - {1}", owner.ArtifactID, owner.Name);
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, ownerString, this.GetType().Name);
            }

            foreach (Services.Field.FieldRef include in searchIncludes)
            {
                string ownerString = string.Format("Include: {0} - {1}", include.ArtifactID, include.Name);
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, ownerString, this.GetType().Name);
            }

            foreach (Services.Field.FieldRef fieldNotIncluded in resultFields.FieldsNotIncluded)
            {
                string ownerString = string.Format("Field: {0} - {1}", fieldNotIncluded.ArtifactID, fieldNotIncluded.Name);
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, ownerString, this.GetType().Name);
            }

            foreach (SearchIndexRef index in searchIndexes)
            {
                string ownerString = string.Format("SearchIndex: {0} - {1}", index.ArtifactID, index.Name);
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, ownerString, this.GetType().Name);
            }

            foreach (Services.Field.FieldRef searchConditionField in searchConditionFields)
            {
                string searchConditionString = string.Format("SearchConditionField: {0} - {1}", searchConditionField.ArtifactID, searchConditionField.Name);
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, searchConditionString, this.GetType().Name);
            }

            // Find a SearchConditionField that contains sub conditions. ("Batch" contains sub conditions) 
            Services.Field.FieldRef batchField = searchConditionFields.Where(x => x.Name == "Batch").FirstOrDefault();
            List<Services.Field.FieldRef> subConditionFieldsForConditionField = await proxy.GetFieldsForObjectCriteriaCollectionAsync(this.SampleWorkspace_ID, batchField, (int)ArtifactType.Document);
            foreach (Services.Field.FieldRef subConditionField in subConditionFieldsForConditionField)
            {
                string searchConditionString = string.Format("SubConditionField: {0} - {1}", subConditionField.ArtifactID, subConditionField.Name);
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, searchConditionString, this.GetType().Name);
            }

            success = true;
        }
        catch (ServiceException exception)
        {
            this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, exception.Message, this.GetType().Name);
        }
    }

    return success;
}

The following example illustrates how to use GetAccessStatusAsync(). When calling the method, specify the workspace Artifact ID, the saved search Artifact ID, and a collection of Artifact ID representing the folder path where the search resides.

public async Task<bool> GetAccessStatusAsync_AnalyticsSearch(IHelper helper)
{
    bool success = false;

    using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
    {
        Logging.ISampleLogger logger = _logger.ForContext("MethodName", new StackFrame(0).GetMethod().Name, false);

        int? savedSearchIdToGetAccessStatusTo;
        bool savedSearchCreated = Data.SavedSearchHelper.TryCreate(proxy, this.SampleWorkspace_ID, "My AnalyticsSearch", out savedSearchIdToGetAccessStatusTo);
        if (!savedSearchCreated)
        {
            logger.LogError("Could not create Analytics Search to get access status for");
        }
        else
        {
            try
            {
                SearchContainer searchContainerSamples = new ServiceHost.SearchContainer(this.SampleData);
                List<int> searchAncestorsIDs = await searchContainerSamples.GetSavedSearchAncestorIDList(savedSearchIdToGetAccessStatusTo.Value, helper);
                SearchAccessStatus accessStatus = await proxy.GetAccessStatusAsync(this.SampleWorkspace_ID, savedSearchIdToGetAccessStatusTo.Value, searchAncestorsIDs);
                logger.LogInformation($"ServiceHost call to AnalyticsSearchManager.{nameof(proxy.GetAccessStatusAsync)} was successful");
                success = true;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, $"ServiceHost call to AnalyticsSearchManager.{nameof(proxy.GetAccessStatusAsync)} was not successful");
            }
        }
    }

    return success;
}

Note: With GetAccessStatusAsync(), you can also specify an empty collection of ancestor Artifact IDs to test the existence of the search not relative of any folder structure.

Email link to AnalyticsSearch

Relativity allows you to generate an email link to saved search results. This example illustrates how to use the GetEmailToLinkUrlAsync() method of the IAnalyticsSearchManager interface to return the email link to an analytics search.

public async Task<bool> GetEmailToLinkUrlAsync_AnalyticsSearch(Client.SamplesLibrary.Helper.IHelper helper)
{
    bool success = false;

    //Get a connection to the API using the API helper classes, available in Event Handlers,
    //Agents, and Custom Pages. They are mocked here for samples purposes
    //NOTE: We are executing under the context of the current user. For more info, see the APIHelper documentation
    using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
    {
        int? savedSearchID;
        if (Client.SamplesLibrary.Data.SavedSearchHelper.TryCreate(proxy, this.Logger, this.SampleWorkspace_ID, "My AnalyticsSearch", out savedSearchID))
        {
            try
            {
                string emailToLinkUrl = await proxy.GetEmailToLinkUrlAsync(this.SampleWorkspace_ID, (int)savedSearchID);
                string unescapeDataString = Uri.UnescapeDataString(emailToLinkUrl);
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Debug, new StackFrame(0).GetMethod().Name, unescapeDataString, this.GetType().Name);
                success = true;
            }
            catch (ServiceException exception)
            {
                this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, exception.Message, this.GetType().Name);
            }
        }
        else
        {
            this.Logger.LogMessage(Client.SamplesLibrary.Logging.LogLevel.Error, new StackFrame(0).GetMethod().Name, "GetEmailToLinkUrl failed", this.GetType().Name);
        }
    }

    return success;
}

Move an AnalyticsSearch

You can use the MoveAsync() method to move an AnalyticsSearch to a different folder. This method requires that you pass the following parameters:

  • Artifact ID of the workspace that contains the search.
  • Artifact ID of the search that you want to move.
  • Artifact ID of the destination folder. To move a saved search to root, specify ...

Note: You must have delete permission for saved search and search folder on the source search folder and add permissions for saved search and search folder on destination folder. If any of those is not met then a validation error is returned.

The SavedSearchMoveResultSet.ProcessState object returned by the operation can have these values:

  • Creating destination container hierarchy
  • Creating search batches
  • Moving saved searches
  • Error
  • Completed

You can also use the overloaded constructors of MoveAsync() to pass the cancellation token and progress object as parameters to this method. The use of cancellation and progress reporting with the MoveAsync() method is similar to the processes followed by the ExecuteAsync() method on the Pivot Manager service.

The following code sample illustrates how to call the MoveAsync() method on the proxy, and pass the required parameters to it.

public bool MoveAsync_AnalyticsSearch(IHelper helper)
{
	kCura.Relativity.Client.SamplesLibrary.Logging.ISampleLogger logger = _logger.ForContext("MethodName", new StackFrame(0).GetMethod().Name, false);

	bool success = false;

	using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
	{
		int? savedSearchID;
		bool savedSearchSuccess = Client.SamplesLibrary.Data.SavedSearchHelper.TryCreate(proxy, SampleWorkspace_ID, "My Analytics Search", out savedSearchID);
		if (!savedSearchSuccess)
		{
			logger.LogError("Could not create Analytics Search to move");
			return success;
		}

		try
		{
			//Artifact ID of active workspace
			int workspaceArtifactId = SampleWorkspace_ID;
			int savedSearchToMoveArtifactID = savedSearchID.Value;
			int destinationContainerArtifactID = SampleSearchContainer_ID;

			SavedSearchMoveResultSet result = proxy.MoveAsync(workspaceArtifactId, savedSearchToMoveArtifactID, destinationContainerArtifactID).Result;
			if (!result.ProcessState.ToLowerInvariant().Contains("error"))
			{
				success = true;
				logger.LogInformation("ServiceHost call to AnalyticsSearchManager.MoveAsync was successful. Returned ProcessState {ProcessState}", result.ProcessState);
			}
			else
			{
				logger.LogError("Error: ServiceHost call to AnalyticsSearchManager.MoveAsync was not successful - {Message}", result);
			}
		}
		catch (Exception ex)
		{
			logger.LogError(ex, "Error: ServiceHost call to AnalyticsSearchManager.MoveAsync was not successful");
			success = false;
		}
		return success;
	}
}

Copy an AnalyticsSearch

You can use the CopySingleAsync() method to make of a copy of an existing . The copy is created in the same saved search folder location as the original. The method returns a SavedSearchRef object with the Artifact ID and the name of the copy. The name of the copy is based on the name of the original with an incremented number in brackets, for example Jones Case Documents (1).

This method requires that you pass the following parameters:

  • Artifact ID of the workspace that contains the .
  • Artifact ID of the .

The following code sample illustrates how to call the CopySingleAsync() method on the proxy:

public async Task<bool> CopySingleAsync_AnalyticsSearch(IHelper helper)
{
    bool success = false;

    using (IAnalyticsSearchManager proxy = helper.GetServicesManager().CreateProxy<IAnalyticsSearchManager>(ExecutionIdentity.User))
    {
        Logging.ISampleLogger logger = _logger.ForContext("MethodName", new StackFrame(0).GetMethod().Name, false);

        int? savedSearchID;
        bool savedSearchCreated = Data.SavedSearchHelper.TryCreate(proxy, this.SampleWorkspace_ID, "My AnalyticsSearch", out savedSearchID);
        if (!savedSearchCreated)
        {
            logger.LogError("Could not create Analytics Search to copy");
        }
        else
        {
            try
            {
                SavedSearchRef savedSearchCopy = await proxy.CopySingleAsync(this.SampleWorkspace_ID, savedSearchID.Value);
                logger.LogInformation($"ServiceHost call to AnalyticsSearchManager.{nameof(proxy.CopySingleAsync)} was successful");
                success = true;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, $"ServiceHost call to AnalyticsSearchManager.{nameof(proxy.CopySingleAsync)} was not successful");
            }
        }
    }

    return success;
}

Exception handling

Use kCura.Kepler.Exceptions.ServiceException and kCura.Kepler.Exceptions.ValidationException when interacting with saved search.

ServiceException

When a saved search operation results in an error, it throws a kCura.Kepler.Exceptions.ServiceException, which inherits from the System.Exception class. This Kepler exception class has a Message property, which contains the error that the save search operation threw. It uses the Data property to populate the ResultSet<T> or the WriteResultSet<T> objects with information about the error returned from the underlying CRUD operation. Since the Data object is a dictionary, Relativity retrieves the information from this object using the index of the result set.

The code samples on this page illustrate how to use the kCura.Kepler.Exceptions.ServiceException.

ValidationException

kCura.Kepler.Exceptions.ValidationException is thrown when invalid input (for example, a query condition) cannot be serialized or deserialized.

Additional Resources

DevHelp Community GitHub Release Notes NuGet

Share knowledge with the Relativity developer community.

Access tools and resources to build an application.

Review the most recent product release notes.

Create .NET Apps faster with NuGet.