Commit graph

6126 commits

Author SHA1 Message Date
Arit Amana
11b7255439
Implement Replit Unified Embed (#15992)
* complete implementation and specs

* add error spec

* write clearer spec
2022-01-07 15:00:06 -05:00
Fernando Valverde
6e6c32e92a
Fixes a name conflict in Rpush models (#15978)
* Fixes a name conflict in Rpush models

* Removes unnecessary operator

* Review feedback
2022-01-07 09:38:16 -06:00
Jeremy Friesen
5b10addbb4
Tidying up and documenting Tag model (#15949)
* 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
2022-01-07 09:14:31 -05:00
Zoey Zheng
775bdb7810
Add individual forem name to flag user modal (#15505)
Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
2022-01-07 09:59:53 +07:00
Ben Halpern
723f9f22c0
Fix goal conversion field test typo (#15985)
* Fix goal conversion field test typo

* Fix typo in test too

* Undo testing word change
2022-01-06 16:34:36 -05:00
Arit Amana
a2155464e7
complete implementation and tests (#15977) 2022-01-06 15:49:35 -05:00
Julianna Tetreault
f296b1db00
Add Logo Size Requirements to the Creator Settings Form (#15965)
* Adds logo size requirements to the Creator Settings form

* Update app/views/admin/shared/_logo_upload.html.erb
2022-01-06 12:30:06 -07:00
Jeremy Friesen
bde5064c9c
Skip google analytics when ga_tracking_id is nil (#15967)
* Skip google analytics when ga_tracking_id is nil

This commit involves short-circuiting Google Analytics calls when the
Forem has not configured a `Settings::General.ga_tracking_id`.  Note,
depending on your local `.env` file (or configured ENV variables) you
may have a `GA_TRACKING_ID` value set; I did, it was set to "Optional"
which overrode the database setting.

We set the HTML data properites in two places: [admin.html.erb][1] and
[application.html.erb][2].

In addition, I'm short-circuiting the local fallback analytics call (via
[Stacato][https://github.com/tpitale/staccato]).  My understanding of
Stacato, based on a cursory read, requires a Google Analytics Tracking
ID to work.

And last, we have a one off of javascript for Google Analytics tracking.

This closes #15962.

[1]:528bd2baa6/app/views/layouts/admin.html.erb (L31)
[2]:528bd2baa6/app/views/layouts/application.html.erb (L55)

How to test?

- Check your .env file to see if you have set GA_TRACKING_ID.  If so,
  unset it.
- Start with a fresh Forem instance.

- Open your browser and open the developer tools to inspect the Network
  activity.
- Open the homepage of your local Forem instance (http://localhost:3000)
- Filter your Network results for analytics.  You shouldn't see any.

Also, make sure you're disabling any blockers you might have as that
influences things.

* Commenting out GA_TRACKING_ID

Related to https://github.com/forem/forem/pull/15967

* Favor empty GA_TRACKING_ID env variable
2022-01-06 12:21:07 -05:00
Daniel Uber
acbdfe5df3
Change contact page to open feature requests as discussions (#15975)
fixes #15973
2022-01-06 10:03:34 -06:00
Michael Kohl
1c80304fd5
Remove htmlentities gem (#15958) 2022-01-06 21:14:32 +07:00
Arit Amana
46e6e44076
Implement TwitchTV Unified Embed (#15968)
* still building

* complete twitch unifiedembed implementation

* quick youtube regexp fix

* add some documentation
2022-01-05 18:27:46 -05:00
dependabot[bot]
8bf2b56e42
Bump eslint from 7.32.0 to 8.6.0 (#15926) 2022-01-05 13:47:40 -05:00
Fernando Valverde
a26c86ec9e
Update deliver worker conflict strategy (#15961) 2022-01-05 11:48:22 -06:00
Suzanne Aitchison
528bd2baa6
Tag autocomplete: storybook multi-select autocomplete component (#15796)
* core functionality in place

* fix dark theme background issues

* separate list for aria-live, add delete and blur functionality

* fix issue with input resize on edit

* handle input blur, prevent special characters, tweak keyup to keydown to ensure runs before change event

* group buttons and add default styles

* style tweaks

* fix logic error with insert index

* refactors

* clear suggestions on blur, even if no input value

* tweaks
2022-01-05 15:01:07 +00:00
Jeremy Friesen
b56ae3acbb
Extracting constants and tidying tests (#15946)
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.
2022-01-05 09:19:39 -05:00
zeotuan
59eea24244
add missing unique key to fragment created with map (#14395)
* add missing unique key to fragment created with map

* add appropriate key for fragment

* Remove files removed from main

* After merge cleanup

Co-authored-by: Michael Kohl <me@citizen428.net>
2022-01-05 11:07:33 +00:00
Suzanne Aitchison
228834596a
Add Storybook entry for tag suggestion item (#15811)
* Add UI template for tag suggestion

handle long tag names on small screens

* update hover background
2022-01-05 07:51:44 +00:00
Suzanne Aitchison
54eaee899e
Update tag search and suggest (#15810)
* add needed fields to tag search

* specs

* add badge to tags suggest response (#15840)

* Add badge to tags suggest response

* Update tags_spec.rb

Co-authored-by: Jeremy Friesen <jeremy.n.friesen@gmail.com>

* limit tag search badge to only badge_image

* only unpack badge image if badge exists

Co-authored-by: Dwight Scott <dwight@forem.com>
Co-authored-by: Jeremy Friesen <jeremy.n.friesen@gmail.com>
2022-01-05 07:51:17 +00:00
Arit Amana
4e8cd50952
require tag (#15955) 2022-01-04 20:51:21 -05:00
Michael Kohl
ec9a5adcb5
Remove store_attribute gem (#15923) 2022-01-04 11:15:57 +07:00
Daniel Uber
349ba97a5e
Send custom user agent string when fetching feeds (#15942)
* Send custom user agent string when fetching feeds

dev.to blocks access to the feeds for user agent "Ruby"

Use "Forem Feeds Import" as alternate user agent when fetching feeds.

fixes https://github.com/forem/forem/issues/15939

* Update app/services/feeds/import.rb

Co-authored-by: Michael Kohl <citizen428@forem.com>

Co-authored-by: Michael Kohl <citizen428@forem.com>
2022-01-03 22:09:24 -06:00
Jeremy Friesen
6269aa4108
Proposing a new feed experiment (#15789)
* 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
2022-01-03 14:38:14 -05:00
Jeremy Friesen
72b89aa57c
Updating documentation for LiquidTagBase (#15933)
In addition adding a Rubocop auto-update.
2022-01-03 14:34:37 -05:00
Michael Kohl
7326744313
Remove Storext gem (#15907) 2022-01-03 22:21:04 +07:00
Michael Kohl
466f111712
Remove sign in/up link from navlinks (#15922)
* Remove sign in/up link from navlinks

* Remove translations

* Update spec
2022-01-03 22:20:29 +07:00
Derek Crosson
ae27d15be7
Prevent whitespace unicode characters from being used in article title and tag (#14909)
* 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>
2022-01-03 09:37:03 -05:00
Jeremy Friesen
5430c2e203
Refactoring and documenting class (#15849)
* Refactoring and documenting class

I came to this commit by looking for raw `where` method calls to the
Article object.  Originally, I was looking at `where.not(user_id: :id)`
but found this service class.

In this class, I saw some significant duplication and went with a bit of
refactoring and some documentation.

This refactoring removed some unnecessary guards and calculations (as
documented inline).

* Naming a constant and adding more documentation
2022-01-03 09:30:41 -05:00
Jeremy Friesen
87f7184b45
Favor repurposing existing scope (#15914)
* Favor repurposing existing scope

In addition to using the existing scope, I went ahead and added some
documentation.

* Fixing comment
2022-01-03 09:30:34 -05:00
Jeremy Friesen
4a03b4aaa8
Renaming/rearranging constants for clarity (#15911)
* Renaming/rearranging constants for clarity

As I'm writing about the Unified Embed project and creating
documentation for the upcoming Forem Fest, I realized that the naming
convention created confusion.

This commit is an effort to tidy up that confusion.

* Normalizing implementation pattern
2022-01-03 09:30:11 -05:00
Michael Kohl
f886c758bd
Add crayons_icon_tag helper (#15878) 2022-01-03 09:52:05 +07:00
Andy Zhao
89f3dbb786
Update trusted user welcome email copy with Discord link (#15903)
* Update trusted user welcome email copy with Discord link

* Only show Discord link for dev.to

* Add accidentally removed line oops
2021-12-30 12:28:15 -05:00
Daniel Uber
cd835c281f
Periodic Digest Email frequency should be in days (#15904)
* Use the most recent timestamp from sent digest messages

The original logic here had queried for up to 10 (unordered) messages
from the database for this user, perhaps leveraging the knowledge that
we purge old messages after 90 days to ensure 10 was enough.

Since the last message in the limited and unordered list could have
had any sent_at time in the past 90 days, it's possible users could
pass the "should send email" check multiple times in a day (and get
multiple digest emails, I'm not certain when the rake task runs but it
could be as frequently as once per deployment?)

Since we're only using this list of messages to find the most recently
sent message, select the maximum sent time.

* convert periodic_email_digest setting to days

Time.current - last_email_sent_at gives an integer count of elapsed
seconds

Settings::General.periodic_email_digest is an integer count of days
between digests.

Convert to days (so we're only sending digests when enough time has
elapsed) instead of defaulting to once every 2 seconds (or at least
several times per day).

* Clean up comparison

Rather than subtracting the current time from the last time, and
checking that the difference in time is greater than the periodic
setting, use days.ago to get the timestamp far enough in the past, and
ensure last email was sent before that ( `sent_at < n.days.ago` ).

This seems like it reads clearer than the original implementation.

* Prefer Time.before? to numeric comparison

Change the method name from last_email_sent_at to last_email_sent, so
that the comparison reads like English (the other callers use it as a
number)

And since the code reveals its intention clearly, there's no need for
an inline comment about the next line any more.
2021-12-30 09:23:24 -06:00
yheuhtozr
3acd4e9deb
delete and add back i18n keys (#15905) 2021-12-30 10:12:40 -05:00
Jeremy Friesen
973ff20eb8
Refactoring to consolidate logic (#15851)
* 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
2021-12-29 11:08:04 -05:00
Daniel Uber
ba590c1750
Load article before creating a page view for an invalid one (#15898)
* Load article before creating a page view for an invalid one

This should resolve a validation error in PageView.create! when the
article does not exist (perhaps it was deleted, or the user suspended,
or it was invalid data sent from the client).

This had been happening dozens of times per day for the last 10
months.

* Use an intention revealing symbol instead of calculated id

Since we only require that find_by not find anything, pass a symbol
that's not going to be the id for any article under any conditions.

As an added benefit, this provides a clear indication of the purpose
of the symbol, without needing to mentally confirm that

```sql
  SELECT * FROM articles
  WHERE id IN (
    SELECT (1 + MAX(id)) FROM articles
  ) LIMIT 1
```

actually never gives any articles back.
2021-12-29 09:48:34 -06:00
Jeremy Friesen
f2cf46d426
Refactoring to leverage an Article scope (#15850)
* 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
2021-12-29 09:25:31 -05:00
Mac Siri
0b0902d81d
Add catch to trackFifteenSecondsOnPage() (#15886) 2021-12-28 12:44:36 -05:00
Michael Kohl
5da625cadb
Rubocop fixes (#15892)
* Add missing spaces

* Use filter_map over map + reject/compact

* Simplify FactoryBot calls

* Use to_h with block instead of map + to_h

* Use guard clause
2021-12-28 09:11:41 -06:00
Michael Kohl
06d6b68d9d
Remove webhooks and related code (#15827) 2021-12-28 10:56:37 +07:00
Daniel Uber
648062d69a
Require parameter string before matching time for youtube video ids (#15890)
We are seeing test failures when an id like "aaabbbcccdd1m" match with
"1m" as the time parameter. I think we only want to match the time
when a ?t= or ?start= (and the permissive &t= or &start=, which might
only be part of the larger REGISTRY_REGEXP and either not effective or
not needed for the video id pattern)

The goal here is these should be valid

"aaabbbcccdd"
"aaabbbcccdd?t=1"
"aaabbbcccdd?start=1h23m55s"
"aaabbbcccdd&t=1"

but not these

"aaabbbcccddt=1"
"aaabbbcccddstart=1"
"aaabbbcccdd123456h"

The same logic may be appropriate to backfill into the prior regexp as
well, my immediate concern is with randomly generated 12-15 character
strings from Fake getting matched during testing (where they were
expected to raise an error during id parsing).
2021-12-27 15:04:40 -06:00
Daniel Uber
c879c5b3d8
Remove slash characters from user name search term (#15867)
* 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?
2021-12-27 12:30:36 -06:00
Daniel Uber
4d40ea18a2
When returning a json error, don't use a raw unescaped string (#15879)
The 400 and 403 error pages show json parsing errors in most browsers,
since "Error: Bad Request" is not a valid json body ('"Error: Bad
Request"' would be, or the object with key error and value of the
string which I've selected is _also_ valid).

Do we have frontend code that's looking for this body before it parses
for some reason, or was this just a mistaken copying from the api
controller in the initial PRs (#2293 for not_authorized, and #6248 for
the bad_request method, which may have just replicated the decision
for not_authorized)?
2021-12-27 11:39:15 -06:00
Jeremy Friesen
0b8c09851c
Favoring constant over magic string (#15852) 2021-12-27 11:31:29 -05:00
dependabot[bot]
2d98e3346b
Bump rubocop from 1.23.0 to 1.24.0 (#15872)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Michael Kohl <me@citizen428.net>
2021-12-27 10:04:59 -05:00
Jeremy Friesen
c7902a3261
Favor destroy_by over where.first&.destroy (#15868)
* Favor `destroy_by` over `where.first&.destroy`

We can use [ActiveRecord::Relation.destroy_by][1] instead of the method
chain.

[1]:https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-destroy_by

* Restoring custom behavior

Those constants are not ActiveRecord::Base objects, so don't implement
the method.
2021-12-22 23:55:52 -05:00
Jeremy Friesen
6d37f4303c
Favoring single SQL and computation over enum (#15865)
* 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
2021-12-22 14:59:37 -05:00
Mac Siri
ea037a127a
Add catch to trackAdImpression() (#15864) 2021-12-22 13:12:37 -05:00
Josh Puetz
10c13078cb
User correct username in mobile mention notifications (#15861) 2021-12-22 10:31:22 -06:00
Jeremy Friesen
c75dd69d09
Favoring common method not previously available (#15855)
With this refactor, I'm adding documentation and favoring using a common
method that wasn't available at the time of implementation.

In [this commit][1] we had logic that said "if we have a singular tag
use the cache" otherwise use the join.  At that time, the implementation
of [Article.cached_tag_with][previous] was as follows, allowing only a
singular tag:

```ruby
scope :cached_tagged_with, ->(tag) { where("cached_tag_list ~* ?",
"^#{tag},| #{tag},|, #{tag}$|^#{tag}$") }
```

The [current implementation][current], as of writing this, allows for
multiple tags and is as follows:

```ruby
scope :cached_tagged_with, lambda { |tag|
  case tag
  when String, Symbol
    # In Postgres regexes, the [[:<:]] and [[:>:]] are equivalent to "start of
    # word" and "end of word", respectively. They're similar to `\b` in Perl-
    # compatible regexes (PCRE), but that matches at either end of a word.
    # They're more comparable to how vim's `\<` and `\>` work.
    where("cached_tag_list ~ ?", "[[:<:]]#{tag}[[:>:]]")
  when Array
    tag.reduce(self) { |acc, elem| acc.cached_tagged_with(elem) }
  when Tag
    cached_tagged_with(tag.name)
  else
    raise TypeError, "Cannot search tags for: #{tag.inspect}"
  end
}
```

Given that we are content to use the cached tag in the singular tag
case, it seems safe to say that we're comfortable using it in the
multiple tag case.

[1]:af5a391429 (diff-24503fd25ed68e6ebceee4951bc4f9b255b197278d4aa9d86ef9d5afe3f26bea)
[previous]:af5a391429/app/models/article.rb (L151)
[current]:98e97e7aa8/app/models/article.rb (L212-L227)
2021-12-22 11:00:54 -05:00
Jeremy Friesen
a40efc6bbd
Refactoring questions asked of user (#15762)
* 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
2021-12-21 12:45:12 -05:00