Exemplo n.º 1
0
        /// <summary>
        /// Run a workflow until it is blocked for user input or is terminated
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="entity"></param>
        public void Run(WorkflowInstance instance, ServerEntity entity)
        {
            TraceLog.TraceInfo("Running workflow " + instance.WorkflowType);
            var status = WorkflowActivity.Status.Complete;
            try
            {
                // invoke the workflow and process steps until workflow is blocked for user input
                while (status == WorkflowActivity.Status.Complete)
                {
                    status = Execute(instance, entity);
                }
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Workflow execution failed", ex);
            }

            // if the workflow is done or experienced a fatal error, terminate it
            if (status == WorkflowActivity.Status.WorkflowDone ||
                status == WorkflowActivity.Status.Error)
            {
                try
                {
                    SuggestionsContext.WorkflowInstances.Remove(instance);
                    SuggestionsContext.SaveChanges();
                }
                catch (Exception ex)
                {
                    TraceLog.TraceException("Could not remove workflow instance", ex);
                }
            }
        }
Exemplo n.º 2
0
        private Status GenerateSuggestions(WorkflowInstance workflowInstance, ServerEntity entity, Dictionary<string, string> suggestionList)
        {
            Item item = entity as Item;
            if (item == null)
            {
                TraceLog.TraceError("Entity is not an Item");
                return Status.Error;
            }

            try
            {
                BingSearch bingSearch = new BingSearch();

                // retrieve and format the search template, or if one doesn't exist, use $(Intent)

                string searchTemplate = null;
                if (InputParameters.TryGetValue(ActivityParameters.SearchTemplate, out searchTemplate) == false)
                    searchTemplate = String.Format("$({0})", ActivityVariables.Intent);
                string query = ExpandVariables(workflowInstance, searchTemplate);

                if (String.IsNullOrWhiteSpace(query))
                {
                    TraceLog.TraceInfo("No query to issue Bing");
                    return Status.Complete;
                }

                // make a synchronous webservice call to bing
                //
                // Async has the problem that the caller of this method assumes that a
                // populated suggestionList will come out.  If it doesn't, the state will execute
                // again and trigger a fresh set of suggestions to be generated.  Eventually all
                // queries will return and populate the suggestions DB with duplicate data.
                // This can be fixed once we move to a "real" workflow system such as WF.
                var results = bingSearch.Query(query);
                foreach (var r in results)
                {
                    WebResult result = r as WebResult;
                    if (result != null)
                        suggestionList[result.Title] = result.Url;
                }
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Bing query failed", ex);
                return Status.Error;
            }

            // this activity is typically last and once links have been generated, no need
            // to keep the workflow instance around
            return Status.Complete;
        }
Exemplo n.º 3
0
        private Status GenerateSuggestions(WorkflowInstance workflowInstance, ServerEntity entity, Dictionary<string, string> suggestionList)
        {
            Item item = entity as Item;
            if (item == null)
            {
                TraceLog.TraceError("Entity is not an Item");
                return Status.Error;
            }

            // TODO: get subject attributes from the Contacts folder, Facebook, and Cloud AD
            // these will hang off of the contact as well as in the workflow InstanceData

            // inexact match
            return Status.Pending;
        }
Exemplo n.º 4
0
        private Status GenerateSuggestions(WorkflowInstance workflowInstance, ServerEntity entity, Dictionary<string, string> suggestionList)
        {
            Item item = entity as Item;
            if (item == null)
            {
                TraceLog.TraceError("GenerateSuggestions: non-Item passed in");
                return Status.Error;
            }

            // HACK: hardcode names for now until the graph queries are in place
            foreach (var like in "Golf;Seattle Sounders;Malcolm Gladwell".Split(';'))
            {
                suggestionList[like] = like;
            }

            return Status.Pending;
        }
Exemplo n.º 5
0
        /// <summary>
        /// This is the typical way to execute a workflow.  This implementation
        /// will retrieve any data (e.g. user selection, or a result of a previous Activity)
        /// and pass it into the Process method.
        /// </summary>
        /// <param name="instance">Workflow instance</param>
        /// <param name="entity">Entity to process</param>
        /// <returns>true if completed, false if not</returns>
        public virtual WorkflowActivity.Status Execute(WorkflowInstance instance, ServerEntity entity)
        {
            // get the data for the current state (if any is available)
            // this data will be fed into the Process method as its argument
            List<Suggestion> data = null;
            try
            {
                data = SuggestionsContext.Suggestions.Where(sugg => sugg.WorkflowInstanceID == instance.ID && sugg.State == instance.State).ToList();

                // if there is no suggestion data, indicate this with a null reference instead
                if (data.Count == 0)
                    data = null;
            }
            catch
            {
                // this is not an error state - the user may not have chosen a suggestion
            }

            // return the result of processing the state with the data
            return Process(instance, entity, data);
        }
Exemplo n.º 6
0
 /// <summary>
 /// Check and process the target field - if it is on the item, store the value in the 
 /// state bag and return true
 /// </summary>
 /// <param name="workflowInstance">WorkflowInstance to process</param>
 /// <param name="item">Item to check</param>
 /// <returns>true for success, false if target field was not found</returns>
 protected bool CheckTargetField(WorkflowInstance workflowInstance, Item item)
 {
     if (TargetFieldName == null)
         return false;
     
     // if the target field has been set, this state can terminate
     try
     {
         FieldValue targetField = item.GetFieldValue(TargetFieldName);
         if (targetField != null && targetField.Value != null)
         {
             StoreInstanceData(workflowInstance, OutputParameterName, targetField.Value);
             StoreInstanceData(workflowInstance, ActivityVariables.LastStateData, targetField.Value);
             TraceLog.TraceDetail(String.Format("Target field {0} was set to {1} for activity {2}", TargetFieldName, targetField.Value, Name));
             return true;
         }
     }
     catch (Exception)
     {
         // not an error condition if the target field wasn't found or the value is empty
     }
     return false;
 }
Exemplo n.º 7
0
        private Item MakeContact(WorkflowInstance workflowInstance, Item item, string name)
        {
            DateTime now = DateTime.UtcNow;

            // create the new contact (detached) - it will be JSON-serialized and placed into
            // the suggestion value field
            var itemID = Guid.NewGuid();
            Item contact = new Item()
            {
                ID = itemID,
                Name = name,
                FolderID = item.FolderID,
                ItemTypeID = SystemItemTypes.Contact,
                ParentID = null,
                UserID = item.UserID,
                FieldValues = new List<FieldValue>()
                {
                    new FieldValue() // sources
                    {
                        FieldName = FieldNames.Sources,
                        ItemID = itemID,
                        Value = name == "Mike Abbott" ? "Facebook,Directory" : name == "Mike Smith" ? "Facebook" : name == "Mike Maples" ? "Directory" : "" // hardcode for now
                    },
                    new FieldValue() // facebook ID of the person
                    {
                        FieldName = FieldNames.FacebookID,
                        ItemID = itemID,
                        Value = "100003631822064"  // hardcode to Mike Abbott for now
                    },
                },
                Created = now,
                LastModified = now,
            };

            return contact;
        }
Exemplo n.º 8
0
        private Status GenerateSuggestions(WorkflowInstance workflowInstance, ServerEntity entity, Dictionary<string, string> suggestionList)
        {
            Item item = entity as Item;
            if (item == null)
            {
                TraceLog.TraceError("Entity is not an Item");
                return Status.Error;
            }

            // make sure the subject was identified - if not move the state forward
            string subjectItem = GetInstanceData(workflowInstance, ActivityVariables.Contact);
            if (subjectItem == null)
                return Status.Complete;

            // set up the FB API context
            FBGraphAPI fbApi = new FBGraphAPI();

            // get the current user
            User user = UserContext.GetUser(item.UserID, true);
            if (user == null)
            {
                TraceLog.TraceError("Could not find the user associated with item " + item.Name);
                return Status.Error;
            }

            UserCredential cred = user.GetCredential(UserCredential.FacebookConsent);
            if (cred != null && cred.AccessToken != null)
            {
                fbApi.AccessToken = cred.AccessToken;
            }
            else
            {   // user not having a FB token is not an error condition, but there is no way to generate suggestions
                // just move forward from this state
                return Status.Complete;
            }

            Item subject = null;
            try
            {
                subject = JsonSerializer.Deserialize<Item>(subjectItem);

                // if the subjectItem is a reference, chase it down
                while (subject.ItemTypeID == SystemItemTypes.Reference)
                {
                    FieldValue refID = subject.GetFieldValue(FieldNames.EntityRef);
                    Guid refid = new Guid(refID.Value);
                    subject = UserContext.Items.Include("FieldValues").Single(i => i.ID == refid);
                }
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Could not deserialize subject Item", ex);
                return Status.Error;
            }

            FieldValue fbID = subject.GetFieldValue(FieldNames.FacebookID);
            if (fbID == null || fbID.Value == null)
            {
                TraceLog.TraceError(String.Format("Could not find FacebookID for Contact {0}", subject.Name));
                return Status.Complete;
            }

            try
            {
                // issue the query against the Facebook Graph API
                var results = fbApi.Query(fbID.Value, FBQueries.Likes);
                foreach (var like in results)
                {
                    string name = (string) like[FBQueryResult.Name];
                    suggestionList[name] = name;
                }
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Error calling Facebook Graph API", ex);
                return Status.Complete;
            }

            return Status.Complete;
        }
Exemplo n.º 9
0
        /// <summary>
        /// Process activity data (typically created through a user selection)
        /// </summary>
        /// <param name="workflowInstance">Instance to store data into</param>
        /// <param name="data">Data to process</param>
        protected Status ProcessActivityData(WorkflowInstance workflowInstance, object data)
        {
            var suggList = data as List<Suggestion>;
            if (suggList != null)
            {
                // return true if a user has selected an action
                foreach (var sugg in suggList)
                {
                    if (sugg.ReasonSelected == Reasons.Chosen || sugg.ReasonSelected == Reasons.Like)
                    {
                        StoreInstanceData(workflowInstance, OutputParameterName, sugg.Value);
                        StoreInstanceData(workflowInstance, ActivityVariables.LastStateData, sugg.Value);
                        TraceLog.TraceInfo(String.Format("User selected suggestion {0} in group {1} for activity {2}",
                            sugg.DisplayName, sugg.GroupDisplayName, Name));
                        return Status.Complete;
                    }
                }

                // return false if the user hasn't yet selected an action but suggestions were already generated
                // for the current state (we don't want a duplicate set of suggestions)
                return Status.Pending;
            }

            // if the data can't be cast into a suggestion list, there is a serious error - move the workflow forward
            // (otherwise it will be stuck forever)
            TraceLog.TraceError("Data passed in is not a list of suggestions");
            return Status.Error;
        }
Exemplo n.º 10
0
 /// <summary>
 /// Get the value from the instance data bag, stored by key
 /// </summary>
 /// <param name="workflowInstance">Instance to retrieve the data from</param>
 /// <param name="key">Key of the value to return</param>
 /// <returns>Value for the key</returns>
 protected static string GetInstanceData(WorkflowInstance workflowInstance, string key)
 {
     if (key == null)
         return null;
     JsonValue dict = JsonValue.Parse(workflowInstance.InstanceData);
     try
     {
         return (string) dict[key];
     }
     catch (Exception)
     {
         return null;
     }
 }
Exemplo n.º 11
0
        /// <summary>
        /// This function takes a string containing a JSON-serialized array of terms
        /// Each term may have zero or more variables defined (bracketed in "$()") 
        /// At the end of execution, all variables will be bound from the workflow's InstanceData and
        /// the resultant (space-concatenated) string returned.  If an unbound variable is found, the term is discarded.
        /// Ex: ["Choose from", "$(Subject)'s", "likes"] will return "Choose from Mike's likes" if Subject is
        /// bound to "Mike", otherwise will return "Choose from likes".
        /// </summary>
        /// <param name="workflowInstance"></param>
        /// <param name="stringTemplate"></param>
        /// <returns></returns>
        protected static string FormatStringTemplate(WorkflowInstance workflowInstance, string stringTemplate)
        {
            if (stringTemplate == null)
                return "";

            try
            {
                JArray array = JArray.Parse(stringTemplate);
                StringBuilder returnString = new StringBuilder();

                foreach (var str in array)
                {
                    returnString.Append(ExpandVariables(workflowInstance, str.ToString()));
                    returnString.Append(" ");
                }
                return returnString.ToString().Trim();
            }
            catch (Exception)
            {
                // if the string template is not a JSON-serialized array, then just return the string as-is
                return stringTemplate;
            }
        }
Exemplo n.º 12
0
        /// <summary>
        /// Expand each of the variables in a format string based on the InstanceData
        /// Variable syntax is $(varname)
        /// </summary>
        /// <param name="workflowInstance">Workflow instance to operate over</param>
        /// <param name="formatExpr">Format expression to expand</param>
        /// <returns></returns>
        protected static string ExpandVariables(WorkflowInstance workflowInstance, string formatExpr)
        {
            if (formatExpr == null)
                return "";

            StringBuilder returnString = new StringBuilder();

            // successively substitute all variables until none are left, or an
            // unbound variable is found.  If the latter, ignore the whole term
            string newExpr = SubstituteNextVariable(workflowInstance, formatExpr);
            while (newExpr != null && newExpr != formatExpr)
            {
                formatExpr = newExpr;
                newExpr = SubstituteNextVariable(workflowInstance, formatExpr);
            }
            if (newExpr != null)
                returnString.Append(newExpr);

            return returnString.ToString();
        }
Exemplo n.º 13
0
        /// <summary>
        /// Canned Execution method for an Activity for processing Items.  This method will:
        ///   1. validate the entity as an Item
        ///   2. verify the item type
        ///   3. check whether the target field is set on the Item and set state appropriately
        ///   4. check whether the user made a selection (via the data parameter) and set state appropriately
        ///   5. if none of this is true, add a set of suggestions from a suggestion function passed in
        /// </summary>
        /// <param name="workflowInstance">Workflow instance to process</param>
        /// <param name="entity">Entity to process</param>
        /// <param name="data">User selection data passed in</param>
        /// <param name="suggestionFunction">Suggestion function to invoke</param>
        /// <returns>return value for the Function</returns>
        protected Status Execute(
            WorkflowInstance workflowInstance, 
            ServerEntity entity, 
            object data,
            Guid expectedItemType,
            Func<WorkflowInstance, ServerEntity, Dictionary<string, string>, Status> suggestionFunction)
        {
            Item item = entity as Item;
            if (item == null)
            {
                TraceLog.TraceError("Entity is not an Item");
                return Status.Error;
            }

            if (VerifyItemType(item, expectedItemType) == false)
                return Status.Error; 

            // if the target field has been set, this state can terminate
            if (CheckTargetField(workflowInstance, item))
                return Status.Complete;

            // check for user selection
            if (data != null)
                return ProcessActivityData(workflowInstance, data);

            return CreateSuggestions(workflowInstance, entity, suggestionFunction);
        }
Exemplo n.º 14
0
        /// <summary>
        /// Create a set of suggestions from the results of calling the suggestion function
        /// </summary>
        /// <param name="workflowInstance">Workflow instance to operate over</param>
        /// <param name="item">Item to process</param>
        /// <param name="suggestionFunction">Suggestion generation function</param>
        /// <returns>Complete if an exact match was found, Pending if multiple suggestions created</returns>
        protected Status CreateSuggestions(
            WorkflowInstance workflowInstance, 
            ServerEntity entity, 
            Func<WorkflowInstance, ServerEntity, Dictionary<string,string>, Status> suggestionFunction)
        {
            // analyze the item for possible suggestions
            var suggestions = new Dictionary<string, string>();
            Status status = suggestionFunction.Invoke(workflowInstance, entity, suggestions);
            TraceLog.TraceDetail(String.Format("Retrieved {0} suggestions from activity {1}", suggestions.Count, Name));

            // if the function completed with an error, or without generating any data, return (this is typically a fail-fast state)
            if (status == Status.Error || suggestions.Count == 0)
                return status;

            // if an "exact match" was discovered without user input, store it now and return
            if (status == Status.Complete && suggestions.Count == 1)
            {
                string s = null;
                foreach (var value in suggestions.Values)
                    s = value;
                StoreInstanceData(workflowInstance, ActivityVariables.LastStateData, s);
                StoreInstanceData(workflowInstance, OutputParameterName, s);
                TraceLog.TraceDetail(String.Format("Exact match {0} was found for activity {1}", s, Name));
                return status;
            }

            // construct the group display name
            string groupDisplayName = GroupDisplayName;
            if (groupDisplayName == null)
                groupDisplayName = workflowInstance.State;
            else
                groupDisplayName = FormatStringTemplate(workflowInstance, groupDisplayName);

            // get the suggestion parent ID if available
            var parentID = GetInstanceData(workflowInstance, ActivityVariables.ParentID);

            // add suggestions received from the suggestion function
            try
            {
                int num = 0;
                foreach (var s in suggestions.Keys)
                {
                    // limit to four suggestions
                    if (num++ == 4)
                        break;

                    var sugg = new Suggestion()
                    {
                        ID = Guid.NewGuid(),
                        ParentID = parentID == null ? (Guid?) null : new Guid(parentID),
                        EntityID = entity.ID,
                        EntityType = entity.GetType().Name,
                        WorkflowType = workflowInstance.WorkflowType,
                        WorkflowInstanceID = workflowInstance.ID,
                        State = workflowInstance.State,
                        SuggestionType = SuggestionType,
                        DisplayName = s,
                        GroupDisplayName = groupDisplayName,
                        SortOrder = num,
                        Value = suggestions[s],
                        TimeSelected = null
                    };
                    SuggestionsContext.Suggestions.Add(sugg);

                    TraceLog.TraceDetail(String.Format("Created suggestion {0} in group {1} for activity {2}", s, groupDisplayName, Name));
                }

                SuggestionsContext.SaveChanges();
                return status;
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Activity execution failed", ex);
                return Status.Error;
            }
        }
Exemplo n.º 15
0
        private Status GenerateSuggestions(WorkflowInstance workflowInstance, ServerEntity entity, Dictionary<string, string> suggestionList)
        {
            Item item = entity as Item;
            if (item == null)
            {
                TraceLog.TraceError("Entity is not an Item");
                return Status.Error;
            }

            User user = UserContext.GetUser(item.UserID, true);
            if (user == null)
            {
                TraceLog.TraceError("Could not find the user associated with Item " + item.Name);
                return Status.Error;
            }

            ADGraphAPI adApi = new ADGraphAPI();
            string adRefreshToken = null;

            // check for FB and/or AD credentials
            UserCredential cred = user.GetCredential(UserCredential.FacebookConsent);
            if (cred != null && cred.AccessToken != null) { adApi.FacebookAccessToken = cred.AccessToken; }
            cred = user.GetCredential(UserCredential.CloudADConsent);
            if (cred != null && cred.RenewalToken != null) { adRefreshToken = cred.RenewalToken; }

            if (adApi.FacebookAccessToken == null && adRefreshToken == null)
            {   // user not having either token is not an error condition, but there is no way to generate suggestions
                // just move forward from this state
                return Status.Complete;
            }

            // if a refresh token exists for AD, get an access token from Azure ACS for the Azure AD service
            if (adRefreshToken != null)
            {
                try
                {
                    AccessTokenRequestWithRefreshToken request = new AccessTokenRequestWithRefreshToken(new Uri(AzureOAuthConfiguration.GetTokenUri()))
                    {
                        RefreshToken = adRefreshToken,
                        ClientId = AzureOAuthConfiguration.GetClientIdentity(),
                        ClientSecret = AzureOAuthConfiguration.ClientSecret,
                        Scope = AzureOAuthConfiguration.RelyingPartyRealm,
                    };
                    OAuthMessage message = OAuthClient.GetAccessToken(request);
                    AccessTokenResponse authzResponse = message as AccessTokenResponse;
                    adApi.ADAccessToken = authzResponse.AccessToken;

                    // workaround for ACS trashing the refresh token
                    if (!String.IsNullOrEmpty(authzResponse.RefreshToken))
                    {
                        TraceLog.TraceInfo("Storing new CloudAD refresh token");
                        user.AddCredential(UserCredential.CloudADConsent, authzResponse.AccessToken, null, authzResponse.RefreshToken);
                        UserContext.SaveChanges();
                    }
                }
                catch (Exception ex)
                {
                    TraceLog.TraceException("Could not contact ACS to get an access token", ex);

                    // Facebook credentials are not available
                    if (adApi.FacebookAccessToken == null)
                        return Status.Pending;  // could be a temporary outage, do not move off this state
                }
            }

            // extract a subject hint if one hasn't been discovered yet
            string subjectHint = GetInstanceData(workflowInstance, ActivityVariables.SubjectHint);
            if (String.IsNullOrEmpty(subjectHint))
            {
                try
                {
                    Phrase phrase = new Phrase(item.Name);
                    if (phrase.Task != null)
                    {
                        subjectHint = phrase.Task.Subject;
                        if (!String.IsNullOrWhiteSpace(subjectHint))
                            StoreInstanceData(workflowInstance, ActivityVariables.SubjectHint, subjectHint);
                    }
                }
                catch (Exception ex)
                {
                    TraceLog.TraceException("Could not initialize NLP engine", ex);
                }
            }

            // get contacts from Cloud AD and Facebook via the AD Graph Person service
            // TODO: also get local contacts from the Contacts folder
            try
            {
                var results = adApi.Query(subjectHint ?? "");
                foreach (var subject in results)
                {
                    // serialize an existing contact corresponding to the subject,
                    // or generate a new serialized contact if one wasn't found
                    Item contact = MakeContact(workflowInstance, item, subject);
                    suggestionList[contact.Name] = JsonSerializer.Serialize(contact);
                }
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Could not contact Person Service", ex);
                return Status.Error;
            }

            // inexact match
            return Status.Pending;
        }
Exemplo n.º 16
0
        private Item MakeContact(WorkflowInstance workflowInstance, Item item, ADQueryResult subject)
        {
            DateTime now = DateTime.UtcNow;
            /*
            FieldValue contactsField = GetFieldValue(item, TargetFieldName, true);
            Guid listID = contactsField.Value != null ? new Guid(contactsField.Value) : Guid.NewGuid();

            // if the contacts sublist under the item doesn't exist, create it now
            if (contactsField.Value == null)
            {
                Item list = new Item()
                {
                    ID = listID,
                    Name = TargetFieldName,
                    IsList = true,
                    FolderID = item.FolderID,
                    ItemTypeID = SystemItemTypes.Reference,
                    ParentID = item.ID,
                    UserID = item.UserID,
                    Created = now,
                    LastModified = now,
                };
                contactsField.Value = listID.ToString();
                UserContext.Items.Add(list);
                UserContext.SaveChanges();

                // add a Suggestion with a RefreshEntity FieldName to the list, to tell the UI that the
                // workflow changed the Item
                SignalEntityRefresh(workflowInstance, item);
            }
            */
            // try to find an existing contact using matching heuristic
            var contact = GetContact(item.UserID, subject);

            // if the contact wasn't found, create the new contact (detached) - it will be JSON-serialized and placed into
            // the suggestion value field
            if (contact == null)
            {
                contact = new Item()
                {
                    ID = Guid.NewGuid(),
                    Name = subject.Name,
                    FolderID = item.FolderID,
                    ItemTypeID = SystemItemTypes.Contact,
                    ParentID = null /*listID*/,
                    UserID = item.UserID,
                    FieldValues = new List<FieldValue>(),
                    Created = now,
                    LastModified = now,
                };
            }

            // add various FieldValues to the contact if available
            try
            {
                // add sources
                string sources = String.Join(",", subject.IDs.Select(id => id.Source));
                contact.GetFieldValue(FieldNames.Sources, true).Value = sources;
                // add birthday
                if (subject.Birthday != null)
                    contact.GetFieldValue(FieldNames.Birthday, true).Value = ((DateTime)subject.Birthday).ToString("d");
                // add facebook ID
                string fbID = subject.IDs.Single(id => id.Source == ADQueryResultValue.FacebookSource).Value;
                if (fbID != null)
                    contact.GetFieldValue(FieldNames.FacebookID, true).Value = fbID;
            }
            catch (Exception) { }

            return contact;
        }
Exemplo n.º 17
0
 /// <summary>
 /// This method will add a Suggestion with a RefreshEntity FieldName and State to the Suggestions
 /// for this ServerEntity.  By convention, this will tell the UI to refresh the Entity.  This method
 /// is called when an Activity changes the Item (e.g. a DueDate is parsed out of the Name, a Contacts 
 /// FieldName is created, etc).
 /// </summary>
 /// <param name="workflowInstance"></param>
 /// <param name="entity"></param>
 protected void SignalEntityRefresh(WorkflowInstance workflowInstance, ServerEntity entity)
 {
     var sugg = new Suggestion()
     {
         ID = Guid.NewGuid(),
         EntityID = entity.ID,
         EntityType = entity.GetType().Name,
         WorkflowType = workflowInstance.WorkflowType,
         WorkflowInstanceID = workflowInstance.ID,
         State = SuggestionTypes.RefreshEntity,
         SuggestionType = SuggestionTypes.RefreshEntity,
         DisplayName = SuggestionTypes.RefreshEntity,
         GroupDisplayName = SuggestionTypes.RefreshEntity,
         Value = null,
         TimeSelected = null
     };
     SuggestionsContext.Suggestions.Add(sugg);
     SuggestionsContext.SaveChanges();
 }
Exemplo n.º 18
0
 /// <summary>
 /// Store a value for a key on the instance data bag
 /// </summary>
 /// <param name="workflowInstance">Instance to retrieve the data from</param>
 /// <param name="key">Key to store under</param>
 /// <param name="data">Data to store under the key</param>
 protected void StoreInstanceData(WorkflowInstance workflowInstance, string key, string data)
 {
     if (key == null)
         return;
     JsonValue dict = JsonValue.Parse(workflowInstance.InstanceData);
     dict[key] = data;
     workflowInstance.InstanceData = dict.ToString();
     SuggestionsContext.SaveChanges();
 }
Exemplo n.º 19
0
        /// <summary>
        /// This function finds the next occurrence of a variable in the format $(varname)
        /// It will attempt to substitute a value from the workflow's instance data, using
        /// varname as the key
        /// </summary>
        /// <param name="workflowInstance">Workflow instance to operate over</param>
        /// <param name="formatString">Format string that contains variables to substitute</param>
        /// <returns>null if the variable wasn't bound, the new string if it was (or if no variables were found)</returns>
        private static string SubstituteNextVariable(WorkflowInstance workflowInstance, string formatString)
        {
            if (formatString == null)
                return null;

            StringBuilder returnString = new StringBuilder();
            int pos = formatString.IndexOf("$(");
            if (pos < 0)
                return formatString;
            int end = formatString.IndexOf(')', pos);
            if (end < 0)
                return formatString;
            
            string variableName = formatString.Substring(pos + 2, Math.Max(0, end - pos - 2));
            returnString.Append(formatString.Substring(0, pos));

            string value = GetInstanceData(workflowInstance, variableName);
            if (value == null)
                return null;  // the variable wasn't bound

            returnString.Append(value);
            returnString.Append(formatString.Substring(end + 1));
            return returnString.ToString();
        }
Exemplo n.º 20
0
        /// <summary>
        /// Start a workflow of a certain type, passing it an entity and some instance data to start
        /// </summary>
        /// <param name="userContext"></param>
        /// <param name="suggestionsContext"></param>
        /// <param name="type">String representing the workflow type</param>
        /// <param name="entity">Entity to associate with the workflow</param>
        /// <param name="instanceData">Instance data to pass into the workflow</param>
        public static void StartWorkflow(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, string type, ServerEntity entity, string instanceData)
        {
            WorkflowInstance instance = null;
            try
            {
                Workflow workflow = null;

                // get the workflow definition out of the database
                try
                {
                    var wt = suggestionsContext.WorkflowTypes.Single(t => t.Type == type);
                    workflow = JsonSerializer.Deserialize<Workflow>(wt.Definition);
                }
                catch (Exception ex)
                {
                    TraceLog.TraceException("Could not find or deserialize workflow definition", ex);
                    return;
                }

                // don't start a workflow with no states
                if (workflow.States.Count == 0)
                    return;

                // store the database contexts
                workflow.UserContext = userContext;
                workflow.SuggestionsContext = suggestionsContext;

                // create the new workflow instance and store in the workflow DB
                DateTime now = DateTime.Now;
                instance = new WorkflowInstance()
                {
                    ID = Guid.NewGuid(),
                    EntityID = entity.ID,
                    EntityName = entity.Name,
                    WorkflowType = type,
                    State = workflow.States[0].Name,
                    InstanceData = instanceData ?? "{}",
                    Created = now,
                    LastModified = now,
                    LockedBy = WorkflowHost.Me,
                };
                suggestionsContext.WorkflowInstances.Add(instance);
                suggestionsContext.SaveChanges();

                TraceLog.TraceInfo("Starting workflow " + type);

                // invoke the workflow and process steps until workflow is blocked for user input or is done
                workflow.Run(instance, entity);

                // unlock the workflowinstance
                instance.LockedBy = null;
                suggestionsContext.SaveChanges();
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("StartWorkflow failed", ex);
                if (instance != null && instance.LockedBy == WorkflowHost.Me)
                {
                    // unlock the workflowinstance
                    instance.LockedBy = null;
                    suggestionsContext.SaveChanges();
                }
            }
        }
Exemplo n.º 21
0
        /// <summary>
        /// Factory method to construct a typed activity based on its definition
        /// </summary>
        /// <param name="definition">Json value corresponding to activity definition</param>
        /// <param name="instance">Workflow instance</param>
        /// <returns>typed activity</returns>
        public static WorkflowActivity CreateActivity(JObject definition, WorkflowInstance instance)
        {
            string typeName = (string) definition["Name"];
            WorkflowActivity activity = CreateActivity(typeName);
            if (activity == null)
                return null;

            foreach (var prop in definition)
            {
                switch (prop.Value.Type)
                {
                    case JTokenType.Object: // a sub-object (typically a nested activity) - store as a string
                        activity.InputParameters[prop.Key] = prop.Value.ToString();
                        break;
                    case JTokenType.Array: // treat as a string template and format appropriately
                        activity.InputParameters[prop.Key] = FormatStringTemplate(instance, prop.Value.ToString());
                        break;
                    default: // treat as a string and expand any variables
                        activity.InputParameters[prop.Key] = ExpandVariables(instance, (string)prop.Value.ToString());
                        break;
                }
            }
            return activity;
        }
Exemplo n.º 22
0
        private Status GenerateSuggestions(WorkflowInstance workflowInstance, ServerEntity entity, Dictionary<string, string> suggestionList)
        {
            Item item = entity as Item;
            if (item == null)
            {
                TraceLog.TraceError("Entity is not an Item");
                return Status.Complete;
            }

            // HACK: hardcode names for now until the graph queries are in place
            foreach (var subject in "Mike Maples;Mike Smith;Mike Abbott".Split(';'))
            {
                Item contact = MakeContact(workflowInstance, item, subject);
                suggestionList[subject] = JsonSerializer.Serialize(contact);
            }

            // inexact match
            return Status.Pending;
        }
Exemplo n.º 23
0
        /// <summary>
        /// Default implementation for the Workflow's Process method.
        ///     Get the current state
        ///     Invoke the corresponding action (with the data object passed in)
        ///     Add any results to the Item's SuggestionsList
        ///     Move to the next state and terminate the workflow if it is at the end
        /// </summary>
        /// <param name="instance">Workflow instance</param>
        /// <param name="entity">Entity to process</param>
        /// <param name="data">Extra data to pass to Activity</param>
        /// <returns>true if completed, false if not</returns>
        protected virtual WorkflowActivity.Status Process(WorkflowInstance instance, ServerEntity entity, object data)
        {
            try
            {
                // get current state and corresponding activity
                TraceLog.TraceInfo(String.Format("Workflow {0} entering state {1}", instance.WorkflowType, instance.State));
                WorkflowState state = States.Single(s => s.Name == instance.State);
                //var activity = PrepareActivity(instance, state.Activity, UserContext, SuggestionsContext);

                WorkflowActivity activity = null;
                if (state.Activity != null)
                    activity = WorkflowActivity.CreateActivity(state.Activity);
                else if (state.ActivityDefinition != null)
                    activity = WorkflowActivity.CreateActivity(state.ActivityDefinition, instance);

                if (activity == null)
                {
                    TraceLog.TraceError("Could not find or prepare Activity");
                    return WorkflowActivity.Status.Error;
                }
                else
                {
                    activity.UserContext = UserContext;
                    activity.SuggestionsContext = SuggestionsContext;
                }

                // invoke the activity
                TraceLog.TraceInfo(String.Format("Workflow {0} invoking activity {1}", instance.WorkflowType, activity.Name));
                var status = activity.Function.Invoke(instance, entity, data);
                TraceLog.TraceInfo(String.Format("Workflow {0}: activity {1} returned status {2}", instance.WorkflowType, activity.Name, status.ToString()));
                instance.LastModified = DateTime.Now;

                // if the activity completed, advance the workflow state
                if (status == WorkflowActivity.Status.Complete)
                {
                    // store next state and terminate the workflow if next state is null
                    instance.State = state.NextState;
                    if (instance.State == null)
                    {
                        status = WorkflowActivity.Status.WorkflowDone;
                        TraceLog.TraceInfo(String.Format("Workflow {0} is done", instance.WorkflowType));
                    }
                }
                SuggestionsContext.SaveChanges();

                return status;
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Process failed", ex);
                return WorkflowActivity.Status.Error;
            }
        }
Exemplo n.º 24
0
        private Status CreateContact(WorkflowInstance workflowInstance, Item item)
        {
            DateTime now = DateTime.UtcNow;
            FieldValue contactsField = item.GetFieldValue(TargetFieldName, true);
            Guid listID = contactsField.Value != null ? new Guid(contactsField.Value) : Guid.NewGuid();

            // if the contacts sublist under the item doesn't exist, create it now
            if (contactsField.Value == null)
            {
                Item list = new Item()
                {
                    ID = listID,
                    Name = TargetFieldName,
                    IsList = true,
                    FolderID = item.FolderID,
                    ItemTypeID = SystemItemTypes.Reference,
                    ParentID = item.ID,
                    UserID = item.UserID,
                    Created = now,
                    LastModified = now,
                };
                contactsField.Value = listID.ToString();
                try
                {
                    UserContext.Items.Add(list);
                    UserContext.SaveChanges();
                }
                catch (Exception ex)
                {
                    TraceLog.TraceException("Creating Contact sublist failed", ex);
                    return Status.Error;
                }
            }

            // get the subject out of the InstanceData bag
            Item contact = null;
            try
            {
                var contactString = GetInstanceData(workflowInstance, OutputParameterName);
                contact = JsonSerializer.Deserialize<Item>(contactString);
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Deserializing contact failed", ex);
                return Status.Error;
            }

            // update the contact if it already exists, otherwise add a new contact
            try
            {
                Item dbContact = UserContext.Items.Include("FieldValues").Single(c => c.ID == contact.ID);
                foreach (var fv in contact.FieldValues)
                {
                    // add or update each of the fieldvalues
                    var dbfv = dbContact.GetFieldValue(fv.FieldName, true);
                    dbfv.Copy(fv);
                }
                dbContact.LastModified = now;
            }
            catch (Exception)
            {
                Folder folder = FindDefaultFolder(contact.UserID, contact.ItemTypeID);
                if (folder != null)
                    contact.FolderID = folder.ID;
                UserContext.Items.Add(contact);
            }
            try
            {
                UserContext.SaveChanges();
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Creating or adding Contact failed", ex);
                return Status.Error;
            }

            // add a contact reference to the contact list
            Guid refID = Guid.NewGuid();
            var contactRef = new Item()
            {
                ID = refID,
                Name = contact.Name,
                ItemTypeID = SystemItemTypes.Reference,
                FolderID = item.FolderID,
                ParentID = listID,
                UserID = contact.UserID,
                Created = now,
                LastModified = now,
                FieldValues = new List<FieldValue>()
                {
                    new FieldValue() { FieldName = FieldNames.EntityRef, ItemID = refID, Value = contact.ID.ToString() },
                    new FieldValue() { FieldName = FieldNames.EntityType, ItemID = refID, Value = EntityTypes.Item }
                }
            };
            try
            {
                UserContext.Items.Add(contactRef);
                UserContext.SaveChanges();
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Creating Contact reference failed", ex);
                return Status.Error;
            }

            // add a Suggestion with a RefreshEntity FieldName to the list, to tell the UI that the
            // workflow changed the Item
            SignalEntityRefresh(workflowInstance, item);

            return Status.Complete;
        }
Exemplo n.º 25
0
        private Status GenerateSuggestions(WorkflowInstance workflowInstance, ServerEntity entity, Dictionary<string, string> suggestionList)
        {
            Item item = entity as Item;
            if (item == null)
            {
                TraceLog.TraceError("Entity is not an Item");
                return Status.Error;
            }

            // if an intent was already matched, return it now
            var intentFV = item.GetFieldValue(ExtendedFieldNames.Intent);
            if (intentFV != null && !String.IsNullOrWhiteSpace(intentFV.Value))
            {
                var wt = SuggestionsContext.WorkflowTypes.FirstOrDefault(t => t.Type == intentFV.Value);
                if (wt != null)
                {
                    suggestionList[wt.Type] = wt.Type;
                    return Status.Complete;
                }
            }

            // run NLP engine over the task name to extract intent (verb / noun) as well as a subject hint
            string name = item.Name;
            string verb = null;
            string noun = null;
            string subject = null;
            try
            {
                Phrase phrase = new Phrase(name);
                if (phrase.Task != null)
                {
                    verb = phrase.Task.Verb.ToLower();
                    noun = phrase.Task.Article.ToLower();
                    subject = phrase.Task.Subject;
                    if (!String.IsNullOrWhiteSpace(subject))
                        StoreInstanceData(workflowInstance, ActivityVariables.SubjectHint, subject);
                }
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("Could not initialize NLP engine", ex);
            }

            // if NLP failed (e.g. data file not found), do "poor man's NLP" - assume a structure like <verb> <noun> [{for,with} <subject>]
            if (verb == null)
            {
                string sentence = name.ToLower();

                // remove extra whitespace and filler words
                StringBuilder sb = new StringBuilder();
                foreach (var word in sentence.Split(new char[] { ' ', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    bool add = true;
                    foreach (var filler in "a;an;the".Split(';'))
                        if (word == filler)
                        {
                            add = false;
                            break;
                        }
                    if (add)
                        sb.AppendFormat("{0} ", word);
                }
                sentence = sb.ToString().Trim();

                // poor man's NLP - assume first word is verb, second word is noun
                string[] parts = sentence.Split(' ');

                if (parts.Length >= 2)
                {
                    verb = parts[0];
                    noun = parts[1];
                }
                if (parts.Length >= 4)
                {
                    if (parts[2] == "for" || parts[2] == "with")
                    {
                        // capitalize and store the word following "for" in the SubjectHint workflow parameter
                        subject = parts[3];
                        subject = subject.Substring(0, 1).ToUpper() + subject.Substring(1);
                        StoreInstanceData(workflowInstance, ActivityVariables.SubjectHint, subject);
                    }
                }
            }

            // try to find an intent that exactly matches the verb/noun extracted by the NLP step
            Intent intent = SuggestionsContext.Intents.FirstOrDefault(i => i.Verb == verb && i.Noun == noun);
            if (intent != null)
            {
                var wt = SuggestionsContext.WorkflowTypes.FirstOrDefault(t => t.Type == intent.WorkflowType);
                if (wt != null)
                {
                    suggestionList[intent.WorkflowType] = wt.Type;
                    return Status.Complete;
                }
            }

            // get a list of all approximate matches and surface as suggestions to the user
            var intentList = SuggestionsContext.Intents.Where(i => i.Verb == verb || i.Noun == noun);
            foreach (var i in intentList)
                suggestionList[i.WorkflowType] = i.WorkflowType;

            // if there are no suggestions, we can terminate this workflow
            if (suggestionList.Count == 0)
            {
                TraceLog.TraceError("No possible intents were found, terminating workflow");
                return Status.Error;
            }

            return Status.Pending;
        }