def affected_wf_module_delta_ids(
            cls, workflow: Workflow, old_slugs: List[str],
            new_slugs: List[str]) -> List[Tuple[int, int]]:
        """
        Find WfModule+Delta IDs whose output may change with this reordering.

        Reordering tabs changes the ordering of 'Multitab' params. Any WfModule
        with a 'Multitab' param can change as a result of this delta.

        There are very few 'Multitab' params in the wild: as of 2019-02-12, the
        only one is in "concattabs". TODO optimize this method to look one
        level deep for _only_ 'Multitab' params that depend on the changed
        ordering, not 'Tab' params, essentially making ReorderTabsCommand _not_
        change any WfModules unless there's a "concattabs" module.
        """
        # Calculate `moved_slugs`: just the slugs whose `position` changed.
        #
        # There's no need to re-render a WfModule that only depends on tabs
        # whose `position`s _haven't_ changed: its input tab order certainly
        # hasn't changed.
        first_change_index = None
        last_change_index = None
        for i, old_slug_and_new_slug in enumerate(zip(old_slugs, new_slugs)):
            old_slug, new_slug = old_slug_and_new_slug
            if old_slug != new_slug:
                if first_change_index is None:
                    first_change_index = i
                last_change_index = i
        moved_slugs = set(old_slugs[first_change_index:last_change_index + 1])

        # Figure out which params depend on those.
        graph = DependencyGraph.load_from_workflow(workflow)
        wf_module_ids = graph.get_step_ids_depending_on_tab_slugs(moved_slugs)
        q = models.Q(id__in=wf_module_ids)
        return cls.q_to_wf_module_delta_ids(q)
Beispiel #2
0
    def test_read_graph_happy_path(self):
        workflow = Workflow.objects.create()
        tab1 = workflow.tabs.create(position=0, slug="tab-1")
        tab2 = workflow.tabs.create(position=1, slug="tab-2")

        ModuleVersion.create_or_replace_from_spec(
            {
                "id_name": "simple",
                "name": "Simple",
                "category": "Add data",
                "parameters": [{"id_name": "str", "type": "string"}],
            }
        )

        ModuleVersion.create_or_replace_from_spec(
            {
                "id_name": "tabby",
                "name": "Tabby",
                "category": "Add data",
                "parameters": [{"id_name": "tab", "type": "tab"}],
            }
        )

        wfm1 = tab1.wf_modules.create(
            order=0, slug="step-1", module_id_name="simple", params={"str": "A"}
        )
        wfm2 = tab1.wf_modules.create(
            order=1, slug="step-2", module_id_name="tabby", params={"tab": "tab-2"}
        )
        wfm3 = tab2.wf_modules.create(
            order=0, slug="step-3", module_id_name="simple", params={"str": "B"}
        )

        graph = DependencyGraph.load_from_workflow(workflow)
        self.assertEqual(
            graph.tabs,
            [
                DependencyGraph.Tab("tab-1", [wfm1.id, wfm2.id]),
                DependencyGraph.Tab("tab-2", [wfm3.id]),
            ],
        )
        self.assertEqual(
            graph.steps,
            {
                wfm1.id: DependencyGraph.Step(set()),
                wfm2.id: DependencyGraph.Step(set(["tab-2"])),
                wfm3.id: DependencyGraph.Step(set()),
            },
        )
    def test_read_graph_happy_path(self):
        workflow = Workflow.objects.create()
        tab1 = workflow.tabs.create(position=0, slug="tab-1")
        tab2 = workflow.tabs.create(position=1, slug="tab-2")

        create_module_zipfile(
            "simple",
            spec_kwargs={"parameters": [{
                "id_name": "str",
                "type": "string"
            }]})
        create_module_zipfile(
            "tabby",
            spec_kwargs={"parameters": [{
                "id_name": "tab",
                "type": "tab"
            }]})

        step1 = tab1.wf_modules.create(order=0,
                                       slug="step-1",
                                       module_id_name="simple",
                                       params={"str": "A"})
        step2 = tab1.wf_modules.create(order=1,
                                       slug="step-2",
                                       module_id_name="tabby",
                                       params={"tab": "tab-2"})
        step3 = tab2.wf_modules.create(order=0,
                                       slug="step-3",
                                       module_id_name="simple",
                                       params={"str": "B"})

        self.kernel.migrate_params.side_effect = lambda m, p: p  # return input
        with self.assertLogs(level=logging.INFO):
            graph = DependencyGraph.load_from_workflow(workflow)
        self.assertEqual(
            graph.tabs,
            [
                DependencyGraph.Tab("tab-1", [step1.id, step2.id]),
                DependencyGraph.Tab("tab-2", [step3.id]),
            ],
        )
        self.assertEqual(
            graph.steps,
            {
                step1.id: DependencyGraph.Step(set()),
                step2.id: DependencyGraph.Step(set(["tab-2"])),
                step3.id: DependencyGraph.Step(set()),
            },
        )
Beispiel #4
0
    def affected_steps_from_tab(self, tab: "Tab") -> Q:
        """Filter for Steps depending on `tab`.

        In other words: all Steps that use `tab` in a 'tab' parameter, plus
        all Steps that depend on them.

        This uses the tab's workflow's `DependencyGraph`.
        """
        graph = DependencyGraph.load_from_workflow(tab.workflow)
        tab_slug = tab.slug
        step_ids = graph.get_step_ids_depending_on_tab_slug(tab_slug)

        # You'd _think_ a Delta could change the dependency graph in a way we
        # can't detect. But [adamhooper, 2019-02-07] I don't think it can. In
        # particular, if this Delta is about to create or fix a cycle, then all
        # the nodes in the cycle are there both before _and_ after the change.
        #
        # So assume `step_ids` is complete here. If we notice some modules
        # not updating correctly, we'll have to revisit this. I haven't proved
        # anything, and I don't know whether future Deltas might break this
        # assumption.

        return Q(id__in=step_ids)