Currently (for Ranked Choice only) a javascript exception is raised on very first vote, preventing the results from being rendered requiring a browser refresh (which doesn't error).
Resolves: TypeError: this.args.rankedChoiceOutcome.round_activity is undefined with simple addition of optional chaining operator.
- Uses `helper.renderGlimmer` with GJS to render the `<Poll` component without any widgets
- Moves some logic into component, so that only `@post`, `@poll` and `@titleHTML` need to be passed into the component (no more 'attrs')
- Updates `modifyClass` calls to modern syntax
- Replaces observer in `Post` model with a native setter & tracked property
- Replaced Poll EmberObject instances with TrackedObject
- Updated component tests with new arguments
- Updated some tests to qunit-dom
- Fixed up core `repliesBelow` and `repliesAbove` logic to create post models properly. Previously it was passing 'transformed' versions of posts into the model, which doesn't make sense.
See: https://meta.discourse.org/t/cant-edit-topic-with-poll-bug-occurs/320845?u=merefield
When the Poll is set to "results ON_CLOSE", vote numbers for each option are only streamed to the browser when the vote is Closed. It is therefore not possible to render the Results.
The current issue is that when you refresh the page, for those that have voted the default view is results. For this type of poll this should NOT happen. The Results view in this mode should not be possible to see until closure, even for the Author.
Because the votes are not yet serialised when this kind of poll remains open, an attempt to display results causes a JavaScript exception and in any case does not make logical sense.
So the fix here is making sure the default view, for Polls that have results on close, is the voting view until the Poll is Closed.
I've added a test to cover this scenario.
Additionally, this requires a refresh of the page when the poll admin actions a Close to ensure the results are serialized in.
Initially, the poll results display a maximum of 25 voters per option. If the number of voters exceeds this limit, a button allows users to load additional pages of voters.
When this button is clicked, a method is called to retrieve the additional voter information. However, there was a bug where the local tracked object was not properly updated for ranked choice voters. This is due to the existence of two attributes per option: one indicating whether new data is currently loading, and the other containing the list of voters.
Instead of updating the entire option key with the voters list, the fix requires updating the voters attribute only.
This is a small but critical change. The pull request also includes a new test that significantly increases coverage, addressing this issue and beyond.
What does this add?
===================
This PR adds an extra button to the poll to show the absolute number of
people who voted for each option. This button will only be added for
the single/multi-select bar chart.
Related meta topic: https://meta.discourse.org/t/absolute-numbers-in-polls/32771
Fixes issue with polls not being fully updated by remote vote contributions in (semi-) real-time.
This was down to too great a focus on tracking local state and not accommodating a more data down approach with responsive getters.
This is now implemented.
I've tried hard to minimise the changes whilst making sure the paradigm is properly followed through.
* `@ember/owner` instead of `@ember/application`
* `discourse-i18n` instead of `I18n`
* `{ service } from "@ember/service"` instead of `inject as service`
This PR introduces FormKit, a component-based form library designed to simplify form creation and management. This library provides a single `Form` component, various field components, controls, validation mechanisms, and customization options. Additionally, it includes helpers to facilitate testing and writing specifications for forms.
1. **Form Component**:
- The main component that encapsulates form logic and structure.
- Yields various utilities like `Field`, `Submit`, `Alert`, etc.
**Example Usage**:
```gjs
import Form from "discourse/form";
<template>
<Form as |form|>
<form.Field
@name="username"
@title="Username"
@validation="required"
as |field|
>
<field.Input />
</form.Field>
<form.Field @name="age" @title="Age" as |field|>
<field.Input @type="number" />
</form.Field>
<form.Submit />
</Form>
</template>
```
2. **Validation**:
- Built-in validation rules such as `required`, `number`, `length`, and `url`.
- Custom validation callbacks for more complex validation logic.
**Example Usage**:
```javascript
validateUsername(name, value, data, { addError }) {
if (data.bar / 2 === value) {
addError(name, "That's not how maths work.");
}
}
```
```hbs
<form.Field @name="username" @validate={{this.validateUsername}} />
```
3. **Customization**:
- Plugin outlets for extending form functionality.
- Styling capabilities through propagated attributes.
- Custom controls with properties provided by `form` and `field`.
**Example Usage**:
```hbs
<Form class="my-form" as |form|>
<form.Field class="my-field" as |field|>
<MyCustomControl id={{field.id}} @onChange={{field.set}} />
</form.Field>
</Form>
```
4. **Helpers for Testing**:
- Test assertions for form and field validation.
**Example usage**:
```javascript
assert.form().hasErrors("the form shows errors");
assert.form().field("foo").hasValue("bar", "user has set the value");
```
- Helper for interacting with he form
**Example usage**:
```javascript
await formKit().field("foo").fillIn("bar");
```
5. **Page Object for System Specs**:
- Page objects for interacting with forms in system specs.
- Methods for submitting forms, checking alerts, and interacting with fields.
**Example Usage**:
```ruby
form = PageObjects::Components::FormKit.new(".my-form")
form.submit
expect(form).to have_an_alert("message")
```
**Field Interactions**:
```ruby
field = form.field("foo")
expect(field).to have_value("bar")
field.fill_in("bar")
```
6. **Collections handling**:
- A specific component to handle array of objects
**Example Usage**:
```gjs
<Form @data={{hash foo=(array (hash bar=1) (hash bar=2))}} as |form|>
<form.Collection @name="foo" as |collection|>
<collection.Field @name="bar" @title="Bar" as |field|>
<field.Input />
</collection.Field>
</form.Collection>
</Form>
```
During migration of Poll plugin from widget basis to glimmer, some functionality was inadvertently dropped:
- A single button should appear instead of the dropdown menu when there is only one valid "poll admin" action
- No button should appear when there are no valid "poll admin" actions for current user
This PR restores the original behaviour and adds test coverage (that didn't exist prior to migration, partly why it wasn't caught earlier)
relates to #27204
related meta topic: https://meta.discourse.org/t/what-is-the-gear-button-under-the-poll-for/315477/2
Fixes a flaky test. `afterUpdate` from chart.js is not integrated with the runloop and component lifecycle, as a result we have no guarantee on when it will happen. The easiest change we can do for now is ensuring we actually have the DOM we expect to have, and if not, we exit early.
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
Co-authored-by: merefield <merefield@gmail.com>
The "migration to Glimmer" has been broken out here from #27155 to make the review process less onerous and reduce change risk:
* DEV: migrates most of the widget code to Glimmer in prep for IRV additions
* NB This already incorporates significant amounts of review and feedback from the prior PR.
* NB because there was significant additional feedback relating to older Poll code that I've improved with feedback, there are some additional changes here that are general improvements to the plugin and not specific to IRV nor Glimmer!
* There should be no trace of IRV code here.
Once this is finalised and merged we can continue to progress with #27155.
Follow-up to #24278 that is slightly less trivial.
* Some were "trivial" usages that were missed in the previous PR because the same file that had at least one other non-trivial usage.
* These involve extra arguments or inheritance but I have checked that they seem correct.
In the vast majority of cases, people want poll voters to be public. Previously, the checkbox for this was hidden behind the 'show advanced' settings in the poll builder UI.
This commit makes three changes to improve the experience:
1. Add `public=true|false` to poll markup (previously it would only be added when true
2. Bring the 'public' switch outside the 'show advanced' section for improved visibility
3. Change the default to 'true'
As of #23867 this is now a real package, so updating the imports to
use the real package name, rather than relying on the alias. The
name change in the package name is because `I18n` is not a valid
name as NPM packages must be all lowercase.
This commit also introduces an eslint rule to prevent importing from
the old I18n path.
For themes/plugins, the old 'i18n' name remains functional.
Why this change?
Previously just using the `addToolbarPopupMenuOptionsCallback` plugin
API itself was insufficient because it required the return object to
include an `action` key which only accepted a name of the action
function as a string. This was highly problematic because the action
function had to be defined on the `composer` service which means using
the `modifyClass` API to add the action function. This made the API
awkward to use leading to poor developer experiencec.
What does this change do?
This commit introduces a couple of improvemnts to the API.
1. First the API has been renamed to `addComposerToolbarPopupMenuOption` because
the API no longer accepts a callback function which was quite
redundant. Instead, it now accepts an Object. The
`addToolbarPopupMenuOptionsCallback` API function is deprecated and
will be dropped in Discourse 3.3. Note that passing the API a
function is still supported but will be dropped when the `addToolbarPopupMenuOptionsCallback`
is removed.
2. The `action` key in the Object passed to the function can now be a
function and is passed the `toolbarEvent` object when called.
3. The `condition` on key in the Object passed to the function can now be a
function and is passed the `composer` service when called.