* Add boolean attribute main_image_from_frontmatter to indicate if cover image was set via frontmatter
* use article model spec for main_image_from_frontmatter test cases
* Add data migration script and spec for main_image_from_frontmatter for articles
* Update app/models/article.rb
Co-authored-by: Jeremy Friesen <jeremy.n.friesen@gmail.com>
Co-authored-by: Jeremy Friesen <jeremy.n.friesen@gmail.com>
Co-authored-by: Michael Kohl <me@citizen428.net>
* Base author reaction functionality
* Get logic more in place
* Finalize tests
* Add admin clause for new user points
* Add proper registered_at for seeded user
* Fix regsitered_at in e2e
* Add registered_at across the board in e2e tests
* Update comments
* Update spec/models/reaction_spec.rb
Co-authored-by: Jamie Gaskins <jamie@forem.com>
* Update spec/models/reaction_spec.rb
Co-authored-by: Jamie Gaskins <jamie@forem.com>
* Update spec/models/reaction_spec.rb
Co-authored-by: Jamie Gaskins <jamie@forem.com>
* Put points in constant and refactor points resave logic
Co-authored-by: Jamie Gaskins <jamie@forem.com>
* Initial statement of intent regarding Listings
I want to use this pull request as a means of conveying intended
direction; namely that we want Forem administrators to be able to toggle
off (or on) the Listing feature set.
This refactor is a first, yet incomplete pass, that doesn't make the
code worse. Expect more of a similar vein as we work to put the Listing
feature set behind a feature flag.
Related https://github.com/forem/rfcs/issues/291
* Adding feature flag based on reviewer comment
Prior to this commit, we were treating all NavigationLink attributes as
unique. So, were we to change a position of one of the NavigationLinks
during the add_navigation_links rake task, we would have created a new
NavigationLink (and the only one would have remained).
With this commit, we're introducing the concept of the NavigationLink's
surrogate identity, that is to say if we have two NavigationLink objects
with the same `url` and `name` we should consider them the same
NavigationLink. This allows us to update properties of those
NavigationLinks (via the rake task) without the risk of creating new
entries.
This unblocks PR #16268 which addresses #16076.
* Convert symbol hash keys to strings when calling .perform_async
Fixes a warning from Sidekiq 6.4.0+ about perform_async arguments
which are not equal when passed to perform (`JSON.parse(JSON.dump(arg))`
should equal arg).
This is a safety measure to prevent passing objects (like classes, or
model instances) rather than their representations (like a class name,
or a model's attributes hash).
This warning will be an error in sidekiq 7
* Turn warning into an error in non-production environments
* Use string keys for reaction notification and article fetched
Missed these two on the first pass
* Update example argument hashes for #enqueues_on_correct_queue
Since this calls perform_async under the hood we need to pass json
safe hashes in the test cases as well.
https://github.com/forem/forem/blob/main/spec/workers/shared_examples/enqueues_on_correct_queue.rb
* Convert keys from FollowData#to_h to string before perform_async
I'm not sure enough where else (outside of notification) to_h is being
called, so I'm converting here when building args, rather than in
FollowData#to_h, which might be my next step.
* Let FollowData#to_h return a hash with string keys
Update spec to use string keys as well.
* Make to_h return string keys for ReactionData
Like FollowData, the #to_h method is only used to call
notifications (this is used to enqueue sidekiq jobs).
* Remove a key that was in the hash
Since reaction_data calls to_h, it gets string and not symbol,
keys. Call Hash#except with a key that was actually there.
* Refactoring cached_followed_tags
This refactor consolidates two queries and a loop into a single query.
One thing that is unclear is if we're ever really clearing the cache of
the user by id?
What follows is un-related to the refactor but appears to be related to #14937.
> First, I see we set a cache here:
>
> ebdaaaf15b/app/decorators/user_decorator.rb (L23-L34)
>
> But when I look at what busts the user cache, it doesn’t look like we’re busting it for the above cached location:
>
> ebdaaaf15b/app/services/edge_cache/bust_user.rb (L3-L22)
>
> What if anything am I missing in regards to cache busting?
* Adding some deprecated methods
* Updating documentation
* Bump for travis
* Bump for travis
* Updating comments to fix confusion
NOTE: I might be introducing more confusion, but at least
I'm addressing the prior confusion.
* Bump for travis
* Updating so we don't cache ActiveRecord objects
* Selecting only applicable attributes for caching
* Apply suggestions from code review
Co-authored-by: Michael Kohl <citizen428@forem.com>
* Adjusting code based on feedback
* Bump for travis
* Bump for travis
* Update app/decorators/user_decorator.rb
Co-authored-by: Michael Kohl <citizen428@forem.com>
Co-authored-by: Michael Kohl <citizen428@forem.com>
I saw the following error in Honeybadger:
```
[PROJECT_ROOT]/app/services/notifications/reactions/send.rb:61 :in `call`
old_json_data = notification.json_data
previous_siblings_size = notification.json_data["reaction"]["aggregated_siblings"].size if old_json_data
notification.json_data = json_data
[PROJECT_ROOT]/app/services/notifications/reactions/send.rb:20 :in `call`
[PROJECT_ROOT]/app/workers/notifications/new_reaction_worker.rb:16 :in `perform`
[PROJECT_ROOT]/app/models/notification.rb:84 :in `send_reaction_notification_without_delay`
[PROJECT_ROOT]/app/controllers/reactions_controller.rb:136 :in `destroy_reaction`
[PROJECT_ROOT]/app/controllers/reactions_controller.rb:168 :in `handle_existing_reaction`
[PROJECT_ROOT]/app/controllers/reactions_controller.rb:77 :in `create`
```
As I was exploring the error, I saw that we were making assumptions
about the data coercion. These are valid and somewhat stable
assumptions, but I wanted to look a little deeper into the situation.
This refactor helps consistently negotiate two situations where we're
transforming request-cycle models into lightweight data structures. It
also begins to show a path towards a generalizable "macro" for this behavior.
Related to #2122, #9534
On the DEV.to database, I ran the following SQL:
```sql
SELECT name, short_summary FROM tags WHERE short_summary LIKE '%<%';
```
There were three tags with a `<` in them:
- changelog
- cnc2018
- javascript
This PR will tidy up our short summary as part of saving a tag. It will
also tidy up the existing tags.
* Let override of ToS and CoC show in onboarding
Prior to this commit any changes to the terms or code of conduct were
not reflected in the onboarding links.
With this commit, I'm leveraging a newly created method that first
checks if there's a page for the given slug. If there is, use that
page's copy.
To test:
- Overwrite default /terms page on a Forem
- Navigate to /onboarding or sign up as a new user
- Click on Terms of Use link text
- Review Terms of Use
Closes#15296
* Adding spec around not passing a block
* Removes the Getting Started section and related code from the config
* Removes the admin_manages_configuration_spec.rb
* Removes "Required" tags from Config
* Reverts removal of activateMissingKeysModal
* Removes mandatory.rb
In my quest to find `where(attribute: value)` in controllers and even
services, I stumbled upon this pattern.
As of this commit (and prior) an Article's associated user is it's author.
This change restores setting usernames to something less obnoxious.
Prior to this commit, I would on occassion get the following error in
seeds:
```shell
❯ bin/rails db:seed
Seeding with multiplication factor: 1
1. Creating Organizations.
2. Creating 10 Users.
rake aborted!
ActiveRecord::RecordInvalid: Validation failed: Username is too long (maximum is 30 characters)
./forem/db/seeds.rb:67:in `block (2 levels) in <main>'
./forem/db/seeds.rb:60:in `times'
./forem/db/seeds.rb:60:in `block in <main>'
./forem/app/lib/seeder.rb:30:in `create_if_none'
./forem/db/seeds.rb:57:in `<main>'
<internal:~/.rbenv/versions/3.0.2/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
<internal:~/.rbenv/versions/3.0.2/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:in `require'
-e:1:in `<main>'
Tasks: TOP => db:seed
(See full trace by running task with --trace)
```
Related to work done in #16067
Prior to this commit, we were somewhat naively rendering Hash style data
attributes in our ERB templates. By rendering each hash attribute
separately, we were rendering characters that could break the
javascript (e.g. double hack or backslash `"` or `\`).
By moving to this view_object rendering, we leverage Rails's `to_json`
behavior to ensure properly escaped values. As part of this exercise, I
generalized the method to allow for other places to benefit from this
behavior.
This generalization also helps ensure that we have a more conformant
rendering (e.g. we should always have an :id, :className, and :name
value in our data-info hash).
_Note: I've updated the user's names for Cypress tests as they are more
likely to catch the particular issue than anything else. I assume that
I'm going to break some cypress tests and will need some help fixing
them._
Closes#15916, #14704
Supersedes #15983
How to test locally:
Assuming you have seeded database (e.g. `rails db:seed`), checkout the
"main" branch. Then in `rails console` find a user that's written articles:
```ruby
user = Article.last.user
user.update(name: "\\: #{user.name}")
user.articles.each(&:save)
```
Now, again on the "main" branch, start your application (e.g.,
`bin/startup`).
Then get a logged in and a logged out browser session going. Open your
web inspector and open console. Then go to the local instances homepage
(e.g., http://localhost:3000) and look for JS errors.
On the main branch, you should see an exception around
`JSON.parse(button.data.info)` (assuming that the `user`'s article is
rendered on the homepage).
Then go to the user's page (e.g. https://localhost:3000/:user-slug) and
look for JS parse errors.
On this PR's branch (e.g.,
`jeremyf/take-two-at-resolving-gh-15916`)
you shouldn't see those console errors.
More importantly, the Follow buttons should work.
* Add validations and constants for feed_style and strategy
* Convert text fields w/ static options to dropdowns
* Convert Bootstrap selectpicker to native HTML select tag
* Update test to use valid feed strategy
I was working on another branch and as part of my commit, Rubocop
removed a validation (but not the spec that asserted the validation).
Below is the "non-updating" rubocop offense on the other branch.
```shell
❯ rubocop ./app/models/notification_subscription.rb
Inspecting 1 file
C
Offenses:
app/models/notification_subscription.rb:13:29: C: [Correctable]
Rails/RedundantPresenceValidationOnBelongsTo: Remove explicit presence
validation for notifiable_id.
validates :notifiable_id, presence: true
^^^^^^^^^^^^^^
1 file inspected, 1 offense detected, 1 offense auto-correctable
```
To remediate, I ran:
```shell
> rubocop --only "Rails/RedundantPresenceValidationOnBelongsTo" \
--auto-correct
```
This resolved the `app/models`. Then did some regex magic and removed
the assertions from `spec/models`.
For Forem folks, I wrote a [forem.team post][1] discuss if this is how
we want to proceed.
[1]:https://forem.team/jeremy/rubocop-auto-updating-mayhem-33a6
* Refactoring to add helper method
Prior to this commit, we made view level calls to service modules. This
refactor provides convenience methods on the model.
Furthermore, it addresses a few Rubocop violations that "come along for
the ride."
* Ensuring cached entity squaks like User
* Fixing broken spec
* Fixing typo
* Moving the "Null" user object closer to User
Prior to this commit, we had the presentation concept of a DELETED_USER
in the ApplicationHelper. Further, we did type checks against that
object instead of relying attributes of the object.
With this commit, I moved the "Null" user closer to the User definition
to help highlight the concept that there might be deleted users.
I didn't remove all of the type checks, but did attempt to create a more
"duck-type" object.
Further, I moved away from an OpenStruct which in the past (and perhaps
present) had performance issues.
* Moving DeletedUser into Users module space
* Initial work
* Implement strategy injection into WeightedQueryStrategy
* Modify field_test config and adding variants
* Change orginal to constant and make some other adjustments
* Fix hardcoded test values
* Tidying up and documenting Tag model
Prior to this commit we had a custom `where(alias_for: [nil, ""])`
call. That call highlighted that we lacked a term for a Tag that was
not an alias. As part of this commit, I named that a "concrete" tag.
Further, I added scopes to assist in helping "name" those concepts.
This commit also adds a data migration and utilization of
StringAttributeCleaner to hopefully get away from `alias_for == ""`
situations.
As of writing this commit <2022-01-04 Tue 17:22 UTC>, in DEV.to we had 5
tags with `alias_for == ""`:
- actionshackathon21
- regex
- atlashackathon
- hotwire
- foremfest
In https://dev.to/admin/blazer I ran the following:
```sql
SELECT name FROM tags WHERE alias_for = ''
```
* Renaming concrete to direct
Prior to this commit, we had a couple of references to magic strings
regarding DisplayAdEvent objects. This commit seeks to consolidate that
behavior.
In a handful of cases we had javascript variables, but as these JS files
are `erb` templates, we can inject the constant as well. This reduces
repetition of knowledge.
* Proposing a new feed experiment
This commit involves tweaking a few subtle aspects of the challenger:
1) Remove the sort by published date on the relevance score. The
articles will now be listed in the order of perceived relevance.
2) Disable a few levers that did little. There really aren't any
`articles.featured = true` articles in the database.
3) Gently increase the weight of each comment in a linear manner.
4) Give a little more weight to posts that don't have followed tags.
In addition, it involves renaming the conversions to better convey their
implementation. This renaming helps create more descriptive labels for
the test results at `/admin/abtests`.
This commit also adds logic to repurpose the AbExperiment feed_strategy
logic; I envision adjusting the weighted feed strategy levers with some
frequency.
Per discussions, I'm also disabling the "not logged in" feed testing.
We'll use the LargeForemExperimental for this.
* Flipping attribute name to positive
Mentally "not disabled" is harder to parse than "enabled". This change
helps with setting an optimistic attribute.
* Bump for travis
* Bump for travis
* Removing file committed by mistake
* Extracting method
* test: whitespace unicode characters cannot be used as titles or tags
* feat: add localized error message
* refactor: use localized error message
* fix: whitespace unicode characters cannot be used as titles or tags
* chore: fix locale after merge
* refactor: fix indentation
* Fix spelling of prohibited in method names
Additionally, rubocop removed a redundant user_id validation since
Article belongs to user.
* Fix spelling
Missed one method call on the same line (fixed the first of two
instances needing changes)
* add failing test cases
The reorganization to remove let was due to a limit on nesting rspec
contexts (it inside context inside describe inside describe when
trying to use different titles in let blocks in contexts)
The first test was clarified (the "U+202D" string that looks like the
code for a unicode point is valid, but the character \u202d is
expected to be invalid
The remaining two tests are based on the feedback I'd given in the PR,
failing because we don't assert title is present after removing
unicode whitespace, and we don't actually set the title (gsub is
non-destructive, returning a value).
* Set title to sanitized title
contains_prohibitied_unicode_characters? was using == (which is not a
good match for strings to regexes), use =~ instead
Change invalid example characters in titles from \u202d (bidi override) to
\u200a (hair space)
Assert that the empty title is blank and can't be blank (even though
it contains no prohibitd characters after replacement)
* change the definition of prohibited characters
From the description, it looks like "unicode space characters" was the
desired rejection set. It was unclear what the existing regex was
matching on (I couldn't get the expected examples to match correctly).
Given the intent, I select "unicode space property, except the ascii
space character", which may _also_ be incorrect but passed the tests.
* Remove now-invalid expectation for presence of user id
Since this was redundant (belongs_to user), we no longer will validate
presence of user id, and we should not test for it.
* Only reject bidirectional text controls
The original issue pointed to the BIDI controls as problematic.
While they called out other "whitespace" characters, as we accept a
wider range of input languages, the need to handle non-printing
punctuation (for example a group separating space for some asian
languages).
It's possible a wider list of characters should be added - if that's
the case I suggest this regex and the sanitization be moved to a
standalone class, and each of the recommendations in
https://www.w3.org/TR/unicode-xml/#Charlist be checked for
applicability.
* Check for blank title after sanitizing disallowed characters
Since validate_title modifies the title, and doesn't set error
messages on the model itself, check for non-empty title _after_
potentially removing any disallowed characters.
* Remove invalid characters before validation
Remove the validate_title and contains_prohibited_characters methods,
and center all logic on the remove prohibitied unicode characters
method.
Remove unneeded and unused arguments (input string is always title,
replacement is always removal/empty string).
* handle case when validating null title
If the title's nil, we don't want to call match? since it will fail.
Title will sometimes be nil when validating.
Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
Co-authored-by: Dan Uber <dan@forem.com>
* Refactoring to consolidate logic
Prior to this commit, two controllers had nearly identical chunks of
logic. This refactor extracts the logic to a common and more canonical
location.
* Addressing rubocop's aggressive auto-fix
* Adding spat operator for pluck
* Renaming method for greater clarity
* Refactoring to leverage an Article scope
I've been looking at the queries that involve filtering articles based
on scores. There's several places that seem to be close to consistent,
but have some nuanced difference.
This refactor consolidates one of those "almost the same" cases.
* Bump for travis
* Remove slash characters from user supplied user search input
Prevents an error when the search term includes '\'
PG::SyntaxError: ERROR: syntax error in tsquery
https://app.honeybadger.io/projects/66984/faults/79391397
I had originally thought to add this cleanup to Search::Username but
decided to move it as close to the generated (invalid) query as
possible to prevent alternate paths finding their way here.
* Add spec
Since there's no existing tests for the scope - I put the test code on
the caller (Search::Username) rather than the model (User), this seems reasonable.
* When the term is empty (or only slashes) just return null relation
* Handle nil input (search for nothing) correctly
One of the request specs sends a username search with no query, so we
can't call nil.delete or nil.empty?, use blank? of empty?
* Favoring single SQL and computation over enum
Prior to this commit, we ran a SQL statement to generate a list of
articles then applied some minor randomization.
With this commit, I'm removing the randomization in favor of expected
values, and collapsing the result set into a single inline SQL and some
post query simple arithmatic.
Let's check my math. For each article we sum:
* `article.score`
* `article.comments_count` * 14
* a random number between 0 and 5 (`rand(6)`) with expected value of 2.5
* the tag's (taggings_count + 1) / 2
The new SQL query sums the `article.score` and the
`article.comments_count` * 14. I then reduce the remainder of the
equation. From "for each article sum `(rand(5) + (taggings_count + 1) /
2)`" we have the following:
`article_count * (2.5 + (taggings_count + 1) / 2)`
Which is equivalent to:
`article_count * ((taggings_count + 1 + (2.5 * 2)) / 2)`
Which is equivalent to:
`article_count * ((taggings_count + 6) / 2)`
This change reduces computation time by favoring expected values.
* Fixing broken behavior and adding comments
* Refactoring questions asked of user
In this pull request, I'm extracting and normalizing role-based
questions asked of the user.
Prior to this commit, our codebase has asked two very similar questions
of our user model:
- `user.has_role?(:admin)`
- `user.admin?`
In asking `has_role?(:admin)` we are relying on implementation details
of the rolify gem. In addition, the `has_role?` question asked
throughout controllers or views means that it's harder to create
hieararchies of permissions.
In favoring `user.admin?` as our question, we can use that indirection
as an opportunity to discuss and decide "Should someone with the
`:super_admin` role be `user.admin? == true`?"
The details of this commit is to do three primary things:
1. Ask the `has_role?` questions in "one place" in the code (e.g. the
`Authorizer` module)
2. Extract the role based questions that are on the `User` model and
provde backwards compatable delegation.
3. Structure the code so that it's harder to accidentally call
`user.has_role?` (e.g., make `User#has_role?` and `User#has_any_role?`
private).
This is related to #15624 and the updates are informed by discussion in
PR #15691. This commit supplants #15691.
* Refactoring the liquid tag policy tests
* Fixing typo
* Bump for travis
* Extracting container for allowed tags & attrs
Prior to this commit, we had several different locations in which we
specified ALLOWED_TAGS and ALLOWED_ATTRIBUTES for HTML rendering and
sanitization.
Curious to see how these either intersected or didn't, I opted to
create a container module that allows for us to more readily normalize
these allowed tags and attributes. It's possible that we won't do any
normalization, but this work helps make that easier.
Ideally, I'd love us to contextualize "why did we choose the
tags/attributes we chose?" But for now, I think consolidating these
tags and attributes will help make adding a `details` and `summary` tag
easier.
This relates to forem/rfcs#296
See [Google Sheet][1] for analysis of what tags/attributes are used, the
intersection and union.
[1]:https://docs.google.com/spreadsheets/d/1yj-a1qus1o0o4cj-_gOMP5yteeg-_f3s5z7kvK0Y7RM/edit#gid=0
* Fixing misnamed constant
* Fixing misnamed constant
* Extracting additional HtmlRendering use cases
* Adding comparative documentation for HTML tags
* Fixing broken parameter signature
* Moving constants into MarkdownProcessor