示例#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);
                }
            }
        }
示例#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;
        }
示例#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;
        }
示例#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;
        }
示例#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);
        }
示例#6
0
        /// <summary>
        /// Start workflows associated with a change in one or more of the entity's fields
        /// </summary>
        /// <param name="userContext"></param>
        /// <param name="suggestionsContext"></param>
        /// <param name="entity"></param>
        /// <param name="oldEntity"></param>
        public static void StartTriggerWorkflows(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, ServerEntity entity, ServerEntity oldEntity)
        {
            if (entity == null || oldEntity == null)
                return;

            // only Item property triggers are supported at this time
            Item item = entity as Item;
            Item oldItem = oldEntity as Item;
            if (item != null)
            {
                // go through field by field, and if a field has changed, trigger the appropriate workflow
                ItemType itemType = userContext.ItemTypes.Include("Fields").Single(it => it.ID == item.ItemTypeID);

                foreach (var field in itemType.Fields)
                {
                    object newValue = item.GetFieldValue(field);
                    object oldValue = item.GetFieldValue(field);

                    // skip fields that haven't changed
                    if (newValue == null || newValue.Equals(oldValue))
                        continue;

                    // do field-specific processing for select fields
                    switch (field.Name)
                    {
                        case FieldNames.Name:
                            //disable for now
                            //RestartWorkflow(userContext, suggestionsContext, item, WorkflowNames.NewTask);
                            break;
                    }
                }
            }
        }
示例#7
0
        /// <summary>
        /// Start NewUser/NewFolder/NewItem workflows based on the entity type
        /// </summary>
        /// <param name="userContext"></param>
        /// <param name="suggestionsContext"></param>
        /// <param name="entity"></param>
        public static void StartNewWorkflows(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, ServerEntity entity)
        {
            if (entity == null)
                return;

            // figure out what kind of entity this is
            Item item = entity as Item;
            Folder folder = entity as Folder;
            User user = entity as User;

            // verify there are no workflow instances associated with this item yet
            var wis = suggestionsContext.WorkflowInstances.Where(wi => wi.EntityID == entity.ID).ToList();
            if (wis.Count > 0)
                return;

            if (item != null && item.IsList == false)
            {
                if (item.ItemTypeID == SystemItemTypes.Task)
                    StartWorkflow(userContext, suggestionsContext, WorkflowNames.NewTask, item, null);
                // the Contact and Grocery new item processing happens in ItemProcessor now
                //if (item.ItemTypeID == SystemItemTypes.Contact)
                //    StartWorkflow(userContext, suggestionsContext, WorkflowNames.NewContact, item, null);
                //if (item.ItemTypeID == SystemItemTypes.Grocery)
                //    Workflow.StartWorkflow(userContext, suggestionsContext, WorkflowNames.NewGrocery, item, null);
            }

            if (folder != null)
            {
            }

            if (user != null)
            {
                StartWorkflow(userContext, suggestionsContext, WorkflowNames.NewUser, user, null);
            }
        }
示例#8
0
        /// <summary>
        /// Restart the workflows associated with an entity
        /// </summary>
        /// <param name="userContext"></param>
        /// <param name="suggestionsContext"></param>
        /// <param name="entity"></param>
        /// <param name="workflowType"></param>
        public static void RestartWorkflow(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, ServerEntity entity, string workflowType)
        {
            if (entity == null || workflowType == null)
                return;

            try
            {
                // kill all existing workflows associated with this Item
                // TODO: also need to mark the suggestions associated with this workflow as stale so that they don't
                // show up for the item again.
                var runningWFs = suggestionsContext.WorkflowInstances.Where(wi => wi.EntityID == entity.ID).ToList();
                if (runningWFs.Count > 0)
                {
                    foreach (var wf in runningWFs)
                        suggestionsContext.WorkflowInstances.Remove(wf);
                    suggestionsContext.SaveChanges();
                }
                StartWorkflow(userContext, suggestionsContext, workflowType, entity, null);
            }
            catch (Exception)
            {
                StartWorkflow(userContext, suggestionsContext, workflowType, entity, null);
            }
        }
示例#9
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;
        }
示例#10
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;
            }
        }
示例#11
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;
        }
示例#12
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;
            }
        }
示例#13
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();
 }
示例#14
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);
        }
示例#15
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();
                }
            }
        }
示例#16
0
        /// <summary>
        /// Execute the workflow instances associated with this entity
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="userContext"></param>
        /// <param name="suggestionsContext"></param>
        /// <returns>true if processing happened (and the operation doesn't need to be processed again), 
        /// false if one the workflow instances was locked (causes the message to be reprocessed)</returns>
        public static bool ExecuteWorkflows(UserStorageContext userContext, SuggestionsStorageContext suggestionsContext, ServerEntity entity)
        {
            if (entity == null)
                return true;

            List<WorkflowInstance> wis = null;

            try
            {
                // get all the workflow instances for this Item
                wis = suggestionsContext.WorkflowInstances.Where(w => w.EntityID == entity.ID).ToList();
                if (wis.Count > 0)
                {
                    // if the instance is locked by someone else, stop processing
                    // otherwise lock each of the workflow instances
                    foreach (var instance in wis)
                    {
                        if (instance.LockedBy != null && instance.LockedBy != Me)
                            return false;
                        instance.LockedBy = Me;
                    }
                    suggestionsContext.SaveChanges();

                    // reacquire the lock list and verify they were all locked by Me (if not, stop processing)
                    // projecting locks and not workflow instances to ensure that the database's lock values are returned (not from EF's cache)
                    var locks = suggestionsContext.WorkflowInstances.Where(w => w.EntityID == entity.ID).Select(w => w.LockedBy).ToList();
                    foreach (var lockedby in locks)
                        if (lockedby != Me)
                            return false;

                    // loop over the workflow instances and dispatch the new message
                    foreach (var instance in wis)
                    {
                        Workflow workflow = null;
                        try
                        {
                            var wt = suggestionsContext.WorkflowTypes.Single(t => t.Type == instance.WorkflowType);
                            workflow = JsonSerializer.Deserialize<Workflow>(wt.Definition);
                        }
                        catch (Exception ex)
                        {
                            TraceLog.TraceException("Could not find or deserialize workflow definition", ex);
                            continue;
                        }

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

                        // invoke the workflow and process steps until workflow is blocked for user input or is done
                        workflow.Run(instance, entity);
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                TraceLog.TraceException("ExecuteWorkflows failed", ex);
                return true;
            }
            finally
            {
                // find and unlock all remaining workflow instances that relate to this entity
                // note that a new context is used for this - to avoid caching problems where the current thread
                // believes it is the owner but the database says otherwise.
                var context = Storage.NewSuggestionsContext;
                wis = context.WorkflowInstances.Where(w => w.EntityID == entity.ID).ToList();
                if (wis.Count > 0)
                {
                    // unlock each of the workflow instances
                    foreach (var instance in wis)
                        if (instance.LockedBy == Me)
                            instance.LockedBy = null;
                    context.SaveChanges();
                }
            }
        }
示例#17
0
            // get or create an reference to the given entity in the UserFolder EntityRefs list
            public Item GetEntityRef(User user, ServerEntity entity)
            {
                Item entityRefsList = GetEntityRefsList(user);
                if (entityRefsList == null)
                    return null;

                var entityID = entity.ID.ToString();
                try
                {   // get existing reference to given entity
                    if (storage.Items.Include("FieldValues").Any(i => i.UserID == user.ID && i.FolderID == entityRefsList.FolderID && i.ParentID == entityRefsList.ID &&
                        i.FieldValues.Any(fv => fv.FieldName == FieldNames.EntityRef && fv.Value == entityID)))
                    {
                        return storage.Items.Include("FieldValues").Single(i => i.UserID == user.ID && i.FolderID == entityRefsList.FolderID && i.ParentID == entityRefsList.ID &&
                            i.FieldValues.Any(fv => fv.FieldName == FieldNames.EntityRef && fv.Value == entityID));
                    }
                    else
                    {   // create new reference to given entity
                        DateTime now = DateTime.UtcNow;
                        var entityRefItemID = Guid.NewGuid();
                        var entityRefItem = new Item()
                        {
                            ID = entityRefItemID,
                            Name = entity.Name,
                            FolderID = entityRefsList.FolderID,
                            UserID = user.ID,
                            ItemTypeID = SystemItemTypes.Reference,
                            ParentID = entityRefsList.ID,
                            Created = now,
                            LastModified = now,
                            FieldValues = new List<FieldValue>()
                            {
                                new FieldValue() { ItemID = entityRefItemID, FieldName = FieldNames.EntityRef, Value = entityID },
                                new FieldValue() { ItemID = entityRefItemID, FieldName = FieldNames.EntityType, Value = entity.GetType().Name },
                            }
                        };
                        storage.Items.Add(entityRefItem);
                        storage.SaveChanges();
                        TraceLog.TraceInfo(String.Format("Created entity ref item {0} for user {1}", entity.Name, user.Name));
                        return entityRefItem;
                    }
                }
                catch (Exception ex)
                {
                    TraceLog.TraceException(String.Format("Created entity ref item {0} for user {1}", entity.Name, user.Name), ex);
                    return null;
                }
            }
示例#18
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;
        }
示例#19
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;
        }