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)
Esempio n. 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()),
            },
        )
Esempio n. 3
0
    def affected_wf_modules_from_tab(cls, tab: Tab) -> Q:
        """
        Filter for WfModules depending on `tab`.

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

        This uses the tab's workflow's `DependencyGraph`.
        """
        graph = DependencyGraph.load_from_workflow(tab.workflow)
        tab_slug = tab.slug
        wf_module_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 `wf_module_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=wf_module_ids)
Esempio n. 4
0
    def test_read_graph_happy_path(self, load_module):
        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,
                                      module_id_name='simple',
                                      params={'str': 'A'})
        wfm2 = tab1.wf_modules.create(order=1,
                                      module_id_name='tabby',
                                      params={'tab': 'tab-2'})
        wfm3 = tab2.wf_modules.create(order=0,
                                      module_id_name='simple',
                                      params={'str': 'B'})

        # DependencyGraph.load_from_workflow needs to call migrate_params() so
        # it can check for tab values. That means it needs to load the 'tabby'
        # module.
        class MockLoadedModule:
            def migrate_params(self, schema, values):
                return values

        load_module.return_value = MockLoadedModule()

        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()),
            })