示例#1
0
    def load_clientside_update(self, delta: "Delta") -> clientside.Update:
        """Build state updates for the client to receive over Websockets.

        This is called synchronously. It may access the database. When
        overriding, be sure to call super() to update the most basic data.

        This must be called in a `workflow.cooperative_lock()`.
        """
        return clientside.Update(workflow=clientside.WorkflowUpdate(
            updated_at=delta.workflow.updated_at))
示例#2
0
    def test_try_set_autofetch_disable_autofetch(self, update_user,
                                                 update_workflow):
        update_user.side_effect = async_noop
        update_workflow.side_effect = async_noop

        user = User.objects.create(username="******", email="*****@*****.**")
        UserProfile.objects.create(user=user)
        workflow = Workflow.create_and_init(owner=user, fetches_per_day=72.0)
        step = workflow.tabs.first().steps.create(
            order=0,
            slug="step-1",
            auto_update_data=True,
            update_interval=1200,
            next_update=datetime.datetime.now(),
        )

        response = self.run_handler(
            try_set_autofetch,
            user=user,
            workflow=workflow,
            stepSlug="step-1",
            isAutofetch=False,
            fetchInterval=300,
        )
        self.assertResponse(response, data=None)
        step.refresh_from_db()
        self.assertEqual(step.auto_update_data, False)
        self.assertEqual(step.update_interval, 300)
        self.assertIsNone(step.next_update)
        workflow.refresh_from_db()
        self.assertEqual(workflow.fetches_per_day, 0.0)

        update_workflow.assert_called_with(
            workflow.id,
            clientside.Update(
                workflow=clientside.WorkflowUpdate(fetches_per_day=0.0),
                steps={
                    step.id:
                    clientside.StepUpdate(is_auto_fetch=False,
                                          fetch_interval=300)
                },
            ),
        )
        update_user.assert_called_with(
            user.id,
            clientside.UserUpdate(usage=UserUsage(fetches_per_day=0.0)))
示例#3
0
    def test_try_set_autofetch_happy_path(self, update_user, update_workflow):
        update_user.side_effect = async_noop
        update_workflow.side_effect = async_noop

        user = User.objects.create(username="******", email="*****@*****.**")
        UserProfile.objects.create(user=user)
        workflow = Workflow.create_and_init(owner=user)
        step = workflow.tabs.first().steps.create(order=0, slug="step-1")

        response = self.run_handler(
            try_set_autofetch,
            user=user,
            workflow=workflow,
            stepSlug="step-1",
            isAutofetch=True,
            fetchInterval=19200,
        )
        self.assertResponse(response, data=None)
        step.refresh_from_db()
        self.assertEqual(step.auto_update_data, True)
        self.assertEqual(step.update_interval, 19200)
        self.assertLess(
            step.next_update,
            datetime.datetime.now() + datetime.timedelta(seconds=19202),
        )
        self.assertGreater(
            step.next_update,
            datetime.datetime.now() + datetime.timedelta(seconds=19198),
        )
        workflow.refresh_from_db()
        self.assertEqual(workflow.fetches_per_day, 4.5)

        update_user.assert_called_with(
            user.id,
            clientside.UserUpdate(usage=UserUsage(fetches_per_day=4.5)))
        update_workflow.assert_called_with(
            workflow.id,
            clientside.Update(
                workflow=clientside.WorkflowUpdate(fetches_per_day=4.5),
                steps={
                    step.id:
                    clientside.StepUpdate(is_auto_fetch=True,
                                          fetch_interval=19200)
                },
            ),
        )
示例#4
0
    def to_clientside(
        self,
        *,
        include_tab_slugs: bool = True,
        include_block_slugs: bool = True,
        include_acl: bool = True,
    ) -> clientside.WorkflowUpdate:
        if include_tab_slugs:
            tab_slugs = list(self.live_tabs.values_list("slug", flat=True))
        else:
            tab_slugs = None  # faster (for index page)

        if include_block_slugs:
            block_slugs = list(self.blocks.values_list("slug", flat=True))
        else:
            block_slugs = None  # faster (for index page)

        if include_acl:
            acl = [
                clientside.AclEntry(email=e.email, role=e.role.value)
                for e in self.acl.all()
            ]
        else:
            acl = None  # more privacy (for report-viewer)

        return clientside.WorkflowUpdate(
            id=self.id,
            secret_id=self.secret_id,  # if you can read it, you can link to it
            owner_email=self.owner.email if self.owner else None,
            owner_display_name=user_display_name(self.owner)
            if self.owner else None,
            selected_tab_position=self.selected_tab_position,
            name=self.name,
            tab_slugs=tab_slugs,
            has_custom_report=self.has_custom_report,
            block_slugs=block_slugs,
            public=self.public,
            updated_at=self.updated_at,
            fetches_per_day=self.fetches_per_day,
            acl=acl,
        )
示例#5
0
    def to_clientside(
        self,
        *,
        include_tab_slugs: bool = True,
        include_block_slugs: Bool = True,
        include_acl: Bool = True,
    ) -> clientside.WorkflowUpdate:
        if include_tab_slugs:
            tab_slugs = list(self.live_tabs.values_list("slug", flat=True))
        else:
            tab_slugs = None  # faster (for index page)

        if include_block_slugs:
            block_slugs = list(self.blocks.values_list("slug", flat=True))
        else:
            block_slugs = None  # faster (for index page)

        if include_acl:
            acl = [
                clientside.AclEntry(email=e.email, role=e.role.value)
                for e in self.acl.all()
            ]
        else:
            acl = None  # more privacy (for report-viewer)

        return clientside.WorkflowUpdate(
            id=self.id,
            url_id=self.url_id,
            owner=self.owner,
            is_example=self.example,
            selected_tab_position=self.selected_tab_position,
            name=self.name,
            tab_slugs=tab_slugs,
            has_custom_report=self.has_custom_report,
            block_slugs=block_slugs,
            public=self.public,
            updated_at=self.updated_at,
            acl=acl,
        )
示例#6
0
    def to_clientside(
            self,
            *,
            include_tab_slugs: bool = True) -> clientside.WorkflowUpdate:
        if include_tab_slugs:
            tab_slugs = list(self.live_tabs.values_list("slug", flat=True))
        else:
            tab_slugs = None  # faster (for index page)

        return clientside.WorkflowUpdate(
            id=self.id,
            url_id=self.url_id,
            owner=self.owner,
            is_example=self.example,
            selected_tab_position=self.selected_tab_position,
            name=self.name,
            tab_slugs=tab_slugs,
            public=self.public,
            updated_at=self.last_delta.datetime,
            acl=[
                clientside.AclEntry(email=e.email, can_edit=e.can_edit)
                for e in self.acl.all()
            ],
        )
示例#7
0
    def test_duplicate_empty_tab(self, send_update, queue_render):
        send_update.side_effect = async_noop
        workflow = Workflow.create_and_init()
        tab = workflow.tabs.first()

        cmd = self.run_with_async_db(
            commands.do(
                DuplicateTab,
                workflow_id=workflow.id,
                from_tab=tab,
                slug="tab-2",
                name="Tab 2",
            ))

        # Adds new tab
        cmd.tab.refresh_from_db()
        self.assertFalse(cmd.tab.is_deleted)
        self.assertEqual(cmd.tab.slug, "tab-2")
        self.assertEqual(cmd.tab.name, "Tab 2")
        workflow.refresh_from_db()
        send_update.assert_called_with(
            workflow.id,
            clientside.Update(
                workflow=clientside.WorkflowUpdate(
                    updated_at=workflow.updated_at,
                    tab_slugs=["tab-1", "tab-2"]),
                tabs={
                    "tab-2":
                    clientside.TabUpdate(
                        slug="tab-2",
                        name="Tab 2",
                        step_ids=[],
                        selected_step_index=None,
                    )
                },
            ),
        )

        # Backward: should delete tab
        self.run_with_async_db(commands.undo(workflow.id))
        cmd.tab.refresh_from_db()
        self.assertTrue(cmd.tab.is_deleted)
        workflow.refresh_from_db()
        send_update.assert_called_with(
            workflow.id,
            clientside.Update(
                workflow=clientside.WorkflowUpdate(
                    updated_at=workflow.updated_at, tab_slugs=["tab-1"]),
                clear_tab_slugs=frozenset(["tab-2"]),
            ),
        )

        # Forward: should bring us back
        self.run_with_async_db(commands.redo(workflow.id))
        cmd.tab.refresh_from_db()
        self.assertFalse(cmd.tab.is_deleted)
        workflow.refresh_from_db()
        send_update.assert_called_with(
            workflow.id,
            clientside.Update(
                workflow=clientside.WorkflowUpdate(
                    updated_at=workflow.updated_at,
                    tab_slugs=["tab-1", "tab-2"]),
                tabs={
                    "tab-2":
                    clientside.TabUpdate(
                        slug="tab-2",
                        name="Tab 2",
                        step_ids=[],
                        selected_step_index=None,
                    )
                },
            ),
        )

        # There should never be a render: we aren't changing any module
        # outputs.
        queue_render.assert_not_called()
示例#8
0
async def try_set_autofetch(
    workflow: Workflow,
    stepSlug: str,
    isAutofetch: bool,
    fetchInterval: int,
    scope,
    **kwargs,
):
    """Set step's autofetch settings, or not; respond with temporary data.

    Client-side, the amalgam of races looks like:

        1. Submit form with these `try_set_autofetch()` parameters.
        2. Server sends three pieces of data in parallel:
            a. Update the client state's step
            b. Update the client state's user usage
            c. Respond "ok"
        3. Client waits for all three messages, and shows "busy" until then.
        4. Client resets the form (because the state holds correct data now).

    Unfortunately, our client/server mechanism doesn't have a way to wait for
    _both_ 2a and 2b. (We have a "mutation" mechanism, but it can only wait
    for 2a, not both 2a and 2b.) [2021-06-17] this problem occurs nowhere else
    in our codebase, so we aren't inspired to build a big solution.

    Our hack: we assume that in practice, the client will usually receive
    2a+2b+2c nearly at the same time (since RabbitMQ is fast and the Internet
    is slow). So the client (3) waits for 2c and then waits a fixed duration;
    then (4) assumes 2a and 2b have arrived and resets the form.
    """
    step_slug = str(stepSlug)
    auto_update_data = bool(isAutofetch)
    try:
        update_interval = max(settings.MIN_AUTOFETCH_INTERVAL, int(fetchInterval))
    except (ValueError, TypeError):
        return HandlerError("BadRequest: fetchInterval must be an integer")

    try:
        step, usage = await _do_try_set_autofetch(
            scope, workflow, step_slug, auto_update_data, update_interval
        )  # updates workflow, step
    except AutofetchQuotaExceeded:
        raise HandlerError("AutofetchQuotaExceeded")

    await rabbitmq.send_user_update_to_user_clients(
        workflow.owner_id, clientside.UserUpdate(usage=usage)
    )
    await rabbitmq.send_update_to_workflow_clients(
        workflow.id,
        clientside.Update(
            workflow=clientside.WorkflowUpdate(
                fetches_per_day=workflow.fetches_per_day
            ),
            steps={
                step.id: clientside.StepUpdate(
                    is_auto_fetch=step.auto_update_data,
                    fetch_interval=step.update_interval,
                )
            },
        ),
    )