def upgrade(): # New instances don't need this migration so we can skip this. # All product instances already had this migration applied and therefore # don't need this. # In case this migration IS needed - FIRST upgrade to grapes release, THEN # upgrade to plum and beyond... return today = date.today() # Get all active workflows with no next_cycle_start_date workflows = db.session.query(models.Workflow) \ .filter( models.Workflow.next_cycle_start_date == None, models.Workflow.recurrences == True, models.Workflow.status == 'Active' ).all() from ggrc_workflows.services.workflow_cycle_calculator import \ get_cycle_calculator # Update all workflows. for workflow in workflows: base_date = date.today() calculator = get_cycle_calculator(workflow) workflow.next_cycle_start_date = \ calculator.nearest_start_date_after_basedate(base_date) db.session.add(workflow) # Save db.session.commit()
def upgrade(): # New instances don't need this migration so we can skip this. # All product instances already had this migration applied and therefore # don't need this. # In case this migration IS needed - FIRST upgrade to grapes release, THEN # upgrade to plum and beyond... return today = date.today() # Get all active workflows with no next_cycle_start_date workflows = db.session.query(models.Workflow) \ .filter( models.Workflow.next_cycle_start_date == None, models.Workflow.recurrences == True, models.Workflow.status == 'Active' ).all() from ggrc_workflows.services.workflow_cycle_calculator import \ get_cycle_calculator # Update all workflows. for workflow in workflows: base_date = date.today() calculator = get_cycle_calculator(workflow) workflow.next_cycle_start_date = \ calculator.nearest_start_date_after_basedate(base_date) db.session.add(workflow) # Save db.session.commit()
def update_workflow_state(workflow): today = date.today() calculator = workflow_cycle_calculator.get_cycle_calculator(workflow) # Start the first cycle if min_start_date < today < max_end_date if workflow.status == "Active" and workflow.recurrences and calculator.tasks: start_date, end_date = calculator.workflow_date_range() # Only create the cycle if we're mid-cycle if (start_date <= today <= end_date) \ and not workflow.cycles: cycle = models.Cycle() cycle.workflow = workflow cycle.calculator = calculator # Other cycle attributes will be set in build_cycle. build_cycle( cycle, None, base_date=workflow.non_adjusted_next_cycle_start_date) notification.handle_cycle_created(None, obj=cycle) adjust_next_cycle_start_date(calculator, workflow) db.session.add(workflow) db.session.flush() return if not calculator.tasks: workflow.next_cycle_start_date = None workflow.non_adjusted_next_cycle_start_date = None return for cycle in workflow.cycles: if cycle.is_current: return if workflow.status == 'Draft': return if workflow.status == "Inactive": if workflow.cycles: workflow.status = "Active" db.session.add(workflow) db.session.flush() return # Active workflow with no recurrences and no active cycles, workflow is # now Inactive workflow.status = 'Inactive' db.session.add(workflow) db.session.flush()
def update_workflow_state(workflow): today = date.today() calculator = workflow_cycle_calculator.get_cycle_calculator(workflow) # Start the first cycle if min_start_date < today < max_end_date if workflow.status == "Active" and workflow.recurrences and calculator.tasks: start_date, end_date = calculator.workflow_date_range() # Only create the cycle if we're mid-cycle if (start_date <= today <= end_date) \ and not workflow.cycles: cycle = models.Cycle() cycle.workflow = workflow cycle.calculator = calculator # Other cycle attributes will be set in build_cycle. build_cycle( cycle, None, base_date=workflow.non_adjusted_next_cycle_start_date) notification.handle_cycle_created(None, obj=cycle) adjust_next_cycle_start_date(calculator, workflow) db.session.add(workflow) db.session.flush() return if not calculator.tasks: workflow.next_cycle_start_date = None workflow.non_adjusted_next_cycle_start_date = None return for cycle in workflow.cycles: if cycle.is_current: return if workflow.status == 'Draft': return if workflow.status == "Inactive": if workflow.cycles: workflow.status = "Active" db.session.add(workflow) db.session.flush() return # Active workflow with no recurrences and no active cycles, workflow is # now Inactive workflow.status = 'Inactive' db.session.add(workflow) db.session.flush()
def handle_cycle_post(sender, obj=None, src=None, service=None): # noqa pylint: disable=unused-argument if src.get('autogenerate', False): # When called via a REST POST, use current user. current_user = get_current_user() workflow = obj.workflow obj.calculator = workflow_cycle_calculator.get_cycle_calculator(workflow) if workflow.non_adjusted_next_cycle_start_date: base_date = workflow.non_adjusted_next_cycle_start_date else: base_date = date.today() build_cycle(obj, current_user=current_user, base_date=base_date) adjust_next_cycle_start_date(obj.calculator, workflow, move_forward=True) update_workflow_state(workflow) db.session.add(workflow)
def handle_cycle_post(sender, obj=None, src=None, service=None): # noqa pylint: disable=unused-argument if src.get('autogenerate', False): # When called via a REST POST, use current user. current_user = get_current_user() workflow = obj.workflow obj.calculator = workflow_cycle_calculator.get_cycle_calculator(workflow) if workflow.non_adjusted_next_cycle_start_date: base_date = workflow.non_adjusted_next_cycle_start_date else: base_date = date.today() build_cycle(obj, current_user=current_user, base_date=base_date) adjust_next_cycle_start_date(obj.calculator, workflow, move_forward=True) update_workflow_state(workflow) db.session.add(workflow)
def start_recurring_cycles(): # Get all workflows that should start a new cycle today # The next_cycle_start_date is precomputed and stored when a cycle is created today = date.today() workflows = db.session.query(models.Workflow)\ .filter( models.Workflow.next_cycle_start_date == today, models.Workflow.recurrences == True # noqa ).all() # For each workflow, start and save a new cycle. for workflow in workflows: cycle = models.Cycle() cycle.workflow = workflow cycle.calculator = workflow_cycle_calculator.get_cycle_calculator( workflow) cycle.context = workflow.context # We can do this because we selected only workflows with # next_cycle_start_date = today cycle.start_date = date.today() # Flag the cycle to be saved db.session.add(cycle) if workflow.non_adjusted_next_cycle_start_date: base_date = workflow.non_adjusted_next_cycle_start_date else: base_date = date.today() # Create the cycle (including all child objects) build_cycle(cycle, base_date=base_date) # Update the workflow next_cycle_start_date to push it ahead based on the # frequency. adjust_next_cycle_start_date(cycle.calculator, workflow, move_forward=True) db.session.add(workflow) notification.handle_workflow_modify(None, workflow) notification.handle_cycle_created(None, obj=cycle) db.session.commit() db.session.flush()
def start_recurring_cycles(): # Get all workflows that should start a new cycle today # The next_cycle_start_date is precomputed and stored when a cycle is created today = date.today() workflows = db.session.query(models.Workflow)\ .filter( models.Workflow.next_cycle_start_date == today, models.Workflow.recurrences == True # noqa ).all() # For each workflow, start and save a new cycle. for workflow in workflows: cycle = models.Cycle() cycle.workflow = workflow cycle.calculator = workflow_cycle_calculator.get_cycle_calculator(workflow) cycle.context = workflow.context # We can do this because we selected only workflows with # next_cycle_start_date = today cycle.start_date = date.today() # Flag the cycle to be saved db.session.add(cycle) if workflow.non_adjusted_next_cycle_start_date: base_date = workflow.non_adjusted_next_cycle_start_date else: base_date = date.today() # Create the cycle (including all child objects) build_cycle(cycle, base_date=base_date) # Update the workflow next_cycle_start_date to push it ahead based on the # frequency. adjust_next_cycle_start_date(cycle.calculator, workflow, move_forward=True) db.session.add(workflow) notification.handle_workflow_modify(None, workflow) notification.handle_cycle_created(None, obj=cycle) log_event(db.session) db.session.commit()
def upgrade(): op.add_column( 'workflows', sa.Column('non_adjusted_next_cycle_start_date', sa.Date(), nullable=True)) # If somebody deleted all the tasks we must clear the next cycle start # date workflows = db.session.query(models.Workflow) \ .filter( models.Workflow.next_cycle_start_date != None, models.Workflow.recurrences == True, models.Workflow.status == 'Active', models.Workflow.next_cycle_start_date < date.today() ).all() for workflow in workflows: tasks_start_days = [ task.relative_start_day for tg in workflow.task_groups for task in tg.task_group_tasks ] tasks_end_days = [ task.relative_end_day for tg in workflow.task_groups for task in tg.task_group_tasks ] if ((not all(tasks_start_days) and not all(tasks_end_days)) or (not tasks_start_days and not tasks_end_days)): app.logger.warning( "Removing NCSD from expired WF {} because no tasks are " "set up. Current NCSD: {}".format( workflow.id, workflow.next_cycle_start_date)) workflow.next_cycle_start_date = None db.session.add(workflow) workflows = db.session.query(models.Workflow) \ .filter( models.Workflow.next_cycle_start_date != None, models.Workflow.non_adjusted_next_cycle_start_date == None, models.Workflow.recurrences == True, models.Workflow.status == 'Active', models.Workflow.next_cycle_start_date >= date.today() ).all() for workflow in workflows: tasks_start_days = [ task.relative_start_day for tg in workflow.task_groups for task in tg.task_group_tasks ] tasks_end_days = [ task.relative_end_day for tg in workflow.task_groups for task in tg.task_group_tasks ] # We must skip tasks that don't have start days and end days defined if ((not all(tasks_start_days) and not all(tasks_end_days)) or (not tasks_start_days and not tasks_end_days)): append_msg = "" if workflow.next_cycle_start_date: workflow.next_cycle_start_date = None append_msg += (" Removing existing next cycle start date " "because none are configured.") db.session.add(workflow) app.logger.warning("Skipping active WF {0} because no tasks " "are set up.{1}".format(workflow.id, append_msg)) continue pre_compute_ncsd = workflow.next_cycle_start_date last_cycle_start_date = None if workflow.cycles: last_cycle_start_date = max( [c.start_date for c in workflow.cycles]) if last_cycle_start_date: base_date = last_cycle_start_date else: base_date = base_date.today() base_date = max(base_date, workflow.next_cycle_start_date) calculator = get_cycle_calculator(workflow, base_date=base_date) if workflow.frequency in {"weekly", "monthly"}: nancsd_day = min(v['relative_start'] for v in calculator.reified_tasks.values()) nancsd_month = None else: nancsd_month, nancsd_day = min( v['relative_start'] for v in calculator.reified_tasks.values()) nancsd_date = calculator.relative_day_to_date( relative_day=nancsd_day, relative_month=nancsd_month, base_date=base_date) if last_cycle_start_date: while calculator.adjust_date(nancsd_date) <= last_cycle_start_date: base_date = base_date + calculator.time_delta nancsd_date = calculator.relative_day_to_date( relative_day=nancsd_day, relative_month=nancsd_month, base_date=base_date) else: base_date = base_date - calculator.time_delta while calculator.adjust_date(nancsd_date) <= pre_compute_ncsd: base_date = base_date + calculator.time_delta nancsd_date = calculator.relative_day_to_date( relative_day=nancsd_day, relative_month=nancsd_month, base_date=base_date) workflow.non_adjusted_next_cycle_start_date = nancsd_date workflow.next_cycle_start_date = calculator.adjust_date(nancsd_date) post_compute_ncsd = workflow.next_cycle_start_date start_dates = [ "{}/{}".format(task.relative_start_month, task.relative_start_day) for tg in workflow.task_groups for task in tg.task_group_tasks ] end_dates = [ "{}/{}".format(task.relative_end_month, task.relative_end_day) for tg in workflow.task_groups for task in tg.task_group_tasks ] if pre_compute_ncsd != post_compute_ncsd: app.logger.warning( "Adjusted NCSD for workflow {}. " "Freq: {}, PRE: {}, Last cycle: {}, POST: {}, NON: {}," "tasks start: {}, tasks end: {},".format( workflow.id, workflow.frequency[:2], pre_compute_ncsd, last_cycle_start_date, post_compute_ncsd, workflow.non_adjusted_next_cycle_start_date, start_dates, end_dates)) db.session.add(workflow) # Save db.session.commit()
def test_task_order(self): annually_wf = { "title": "annually thingy", "description": "start this many a time", "frequency": "annually", "task_groups": [ { "title": "task group", "task_group_tasks": [ { 'title': 'annual task 1', "relative_start_day": 21, # 6/21/2015 "relative_start_month": 6, "relative_end_day": 25, # 6/25/2015 Thu "relative_end_month": 6, }, { 'title': 'annual task 2', "relative_start_day": 11, # 6/11/2015 Thu "relative_start_month": 6, "relative_end_day": 16, # 6/16/2015 Tue "relative_end_month": 6, }, { 'title': 'annual task 6', "relative_start_day": 2, # 7/2/2015 Thu "relative_start_month": 7, "relative_end_day": 15, # 7/15/2015 Wed "relative_end_month": 7, }, { 'title': 'annual task 3', "relative_start_day": 3, # 6/3/2015 Wed "relative_start_month": 6, "relative_end_day": 15, # 6/15/2015 Mon "relative_end_month": 6, }, { 'title': 'annual task 4', "relative_start_day": 8, # 6/8/2015 Mon "relative_start_month": 6, "relative_end_day": 15, # 6/15/2015 Mon "relative_end_month": 6, }, { 'title': 'annual task 5', "relative_start_day": 2, # 7/2/2015 Thu "relative_start_month": 6, "relative_end_day": 15, # 6/15/2015 Mon "relative_end_month": 6, } ], "task_group_objects": self.random_objects }, ] } with freezegun.freeze_time("2015-06-01 13:00"): _, wf = self.generator.generate_workflow(annually_wf) active_wf = db.session.query( models.Workflow).filter(models.Workflow.id == wf.id).one() calculator = get_cycle_calculator(active_wf) self.assertEqual( [2, 3, 8, 11, 21, 2], [task.relative_start_day for task in calculator.tasks])