* Avoid using robohash for article cover images
If their service slows down, loading the cover image times out in
system tests.
Use a locally served image file instead.
* Specify expected image format for view object test
We were relying on the internals of Faker to return a robohash.org url
and checking that we included that in the cloudinary url.
Rather than relying on the default behavior, explicitly pass a
robohash url in the main image for this spec.
This is failing for me (and had been flaky previously).
https://app.travis-ci.com/forem/forem/builds/248455560 blocked a
deployment because of an error (`@followsRequest` timed out waiting).
I'm able to reproduce this locally and disabling until we can find a
stable fix.
Prior to this commit the following situation existed:
> The path /dashboard/analytics/org/:id requires user
> authentication (e.g. signed in). However, it does not enforce
> authorization. Anyone can see this page. The page, however, uses
> javascript to populate the data. So no information, aside from the org
> name associated with the :id leaks out. The javascript API end point
> enforces organization membership.
>
> I would expect that the authorization in the HTML rendering would be
> the same as the javascript API end point.
This commit ensures that the dashboards#analytics end point uses the
same policy logic as the API analytics end points. Further, it keeps
folks who aren't org members out of the base HTML page for other orgs.
Closes forem/forem/#16985
There are two existing listeners for the `Audit::Logger`: `:moderator`
and `:internal`. (Note: during tests we ignore the :moderator and
:internal logs as defined in [config/initializers/audit_events.rb][1].)
Using `rg "Audit::Logger\.log\(:internal," --files-with-matches`, the
`:internal` listener is found in:
- app/controllers/admin/secrets_controller.rb
- app/controllers/admin/settings/base_controller.rb
- app/controllers/admin/settings/general_settings_controller.rb
Using `rg "Audit::Logger\.log\(:moderator," --files-with-matches`, the
`:moderator` listener is used in:
- app/controllers/rating_votes_controller.rb
- app/controllers/comments_controller.rb
- app/controllers/stories/pinned_articles_controller.rb
- app/controllers/admin/response_templates_controller.rb
- app/controllers/admin/tags_controller.rb
- app/controllers/admin/articles_controller.rb
- app/controllers/admin/users_controller.rb
- app/controllers/admin/reactions_controller.rb
- app/controllers/admin/tags/moderators_controller.rb
- app/controllers/tag_adjustments_controller.rb
- app/controllers/reactions_controller.rb
The `admin/spaces#update` action is most similar to the `admin#settings`
actions, which is why I chose `:internal`. I am looking for further
guidance on documenting this little area of the application (in
particular providing a data dictionary of :internal and :moderator).
Closesforem/forem#16957
[1]:https://github.com/forem/forem/blob/main/config/initializers/audit_events.rb#L9-L11
This commit provides two things:
1. Some notes related to my analysis regarding the dashboard
2. Conditional redirects and rendering based on article policies
The code comments say most of what I want to say, but to reiterate:
When a user can't create articles nor do they already have published
articles, then we don't want to avoid showing them stats related to
articles.
Closesforem/forem#16913
Related to forem/forem#16908 and forem/forem#16931
* improvements to profile preview card cypress spec
* woops - missed staging files
* make sure follow buttons have been initialized
* wait on follow button fetching on load
* pagination theme for new admin view
* align pagination widget correctly in desktop view
* remove bottom pagination
* re-add pagination to the bottom
* use block styling for the links
We already have a partial unique index for this column scoped on
`published = true`, which is still useful. This index does not make that
index redundant because that index is used to enforce a constraint that
we *only* want to apply to published articles. This index will be used
when `WHERE published` is not part of the query.
* Add a comment, and a safe default value for cloudinary
When moving a site from imgproxy to cloudinary, we observed that
wrapping the cloud name in quotes caused off-looking urls (with the
user name in %22 escaped quotes).
Additionally, if cloudinary is enabled, cloudinary secure should
be set to true. We leave the others blank to prevent conditionally enabling this
service (we check for ENV var presence) mistakenly, but the secure
flag won't turn it on or off and is safe to keep a default value.
* remove unnecessary comment
quoting the env vars had no effect when tested.
As part of my AuthN/AuthZ work I'm reviewing policies. I've been
looking at Dashboard pseudo-policies. The dashboard has some implicit
organization policies that I'm looking to expose and describe.
This refactor simply leverages methods already on the user.
Discovered while working on forem/forem#16985
We only have one reference to the UnauthorizedError, which is shadows
the ApplicationPolicy::NotAuthorizedError. This commit removes the
exception.
Related to forem/forem#16985 but only barely
* Removing a JS message about connect
Related to forem/forem#14734
* Remvoing another reference
I ran `rg Connect[^i]` to see about any remaining references to
Connect. This one seems to be the last.
* Adjusting article copy to be more general
Our language regarding articles needs minor revisions to speak a bit
more generally about content. This follows on the features of
AuthN/AuthZ work to allow forem admins to configure their forems such
that a subset of their forem members may not have the ability to create
articles.
Closesforem/forem#16890Closesforem/forem#16891
* Update app/views/users/_notifications.html.erb
Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
As I'm looking at the dashboard, there's lots of small duplication.
This refactor is a way to help me collect my thoughts regarding how to
approach the larger issue at hand.
This menu item will show up once you have added the
`:limit_post_creation_to_admins` feature flag (regardless of whether
it is enabled or not).
If you wish to add the menu item:
```console
rails console runner "FeatureFlag.add(:limit_post_creation_to_admins)"
```
In "adding" the item, it will default to disabled. However, as an
administrator you should now be able to toggle on and off the
authorization enforcement.
If you wish to remove the menu item:
```console
rails console runner "FeatureFlag.remove(:limit_post_creation_to_admins)"
```
Future plans for this will be to remove the `FeatureFlag.exist?`
conditional so that the menu item shows up. A major consideration is
that we'll assume that all Forem's have a "default space" in which
folks (by default) can post.
Builds on forem/forem#16897Closesforem/forem#16842
* Adding ArticlePolicy#has_existing_articles_or_can_create_new_ones?
As part of our aspirations to only show users what is relevant to them
and "hiding" what is not, this method will help us with the edge case of
"should we show the user a dashboard listing of posts?"
Related to forem/forem#16837
* Adding further documentation
* Adding clarifying comment
This method was making a DB query every time. Chances are that if the
table is there the first time, it'll continue to exist. This is an
assumption generally baked into ActiveRecord - it caches the schema for
every table when its corresponding model is used and never checks it
again for the life of the process.
There is no reason to create an instance variable as we don't pass this
to the view. Further, by adding this as a before_action there's a
disconnect in logic.
This commit attempts to address those issues.
What follows is a three-fold change:
1. Removing the before action (let's just call the method)
2. Reworking the method to reduce, just a bit, the method cost
3. Removing an unused instance variable
Here are the benchmarks. Note the "follows_limit" as written below is
the "Proposed" route.
```ruby
require 'benchmark'
def follows_limit_original(params:, default: 80, max: 1000)
per_page = (params[:per_page] || default).to_i
@follows_limit = [per_page, max].min
end
def follows_limit(params:, default: 80, max: 1000)
return default unless params.key?(:per_page)
per_page = params[:per_page].to_i
return max
per_page
end
def follows_limit_alt(params:, default: 80, max: 1000)
per_page = params.fetch(:per_page, default).to_i
return max if per_page > max
per_page
end
TIMES = 10_000
Benchmark.bmbm do |b|
b.report("Original no params") { 1000.times { follows_limit_original(params: {}) } }
b.report("Original less than max") { 1000.times { follows_limit_original(params: {per_page: 90 }) } }
b.report("Original greater than max") { 1000.times { follows_limit_original(params: {per_page: 9000 }) } }
b.report("Proposed no params") { 1000.times { follows_limit(params: {}) } }
b.report("Proposed less than max") { 1000.times { follows_limit(params: {per_page: 90 }) } }
b.report("Proposed greater than max") { 1000.times { follows_limit(params: {per_page: 9000 }) } }
b.report("Alt no params") { 1000.times { follows_limit_alt(params: {}) } }
b.report("Alt less than max") { 1000.times { follows_limit_alt(params: {per_page: 90 }) } }
b.report("Alt greater than max") { 1000.times { follows_limit_alt(params: {per_page: 9000 }) } }
end
```
```shell
$ ruby /Users/jfriesen/git/forem/bench.rb
Rehearsal -------------------------------------------------------------
Original no params 0.000403 0.000002 0.000405 ( 0.000405)
Original less than max 0.000109 0.000005 0.000114 ( 0.000114)
Original greater than max 0.000161 0.000006 0.000167 ( 0.000166)
Proposed no params 0.000076 0.000001 0.000077 ( 0.000076)
Proposed less than max 0.000114 0.000009 0.000123 ( 0.000126)
Proposed greater than max 0.000112 0.000011 0.000123 ( 0.000123)
Alt no params 0.000104 0.000007 0.000111 ( 0.000114)
Alt less than max 0.000113 0.000009 0.000122 ( 0.000122)
Alt greater than max 0.000111 0.000001 0.000112 ( 0.000115)
---------------------------------------------------- total: 0.001354sec
user system total real
Original no params 0.000108 0.000000 0.000108 ( 0.000110)
Original less than max 0.000109 0.000001 0.000110 ( 0.000111)
Original greater than max 0.000109 0.000000 0.000109 ( 0.000109)
Proposed no params 0.000071 0.000000 0.000071 ( 0.000071)
Proposed less than max 0.000103 0.000001 0.000104 ( 0.000103)
Proposed greater than max 0.000102 0.000000 0.000102 ( 0.000103)
Alt no params 0.000093 0.000000 0.000093 ( 0.000093)
Alt less than max 0.000103 0.000000 0.000103 ( 0.000104)
Alt greater than max 0.000102 0.000000 0.000102 ( 0.000103)
```
* Always show the browse section regardless of featured
* Add tests for /pod
* Use safe operator if there are no episodes to show
* Fix test for new podcast page
* Fix test again for podcast page
* symlink .env_sample as .env.test
* Remove unneeded copy from test setup
Now that there's always a valid environment (.env.test) present, we
don't need the .env file for test setup.
follow on to #15942 which enabled downloading existing DEV feeds, this
allows setting a feed url to DEV.
It's possible this user agent should be brought inline with the
tags choice of community name and url, but in the short term I'm
making this consistent with the existing feed importer.
There is an incredible amount of conditionals in play within this
controller. I'm working to disentangle the logic so we can introduce
unified authorization policies.
The first step is removing the quasi-opaque "before_action" behavior
related to authorization. My strong preference is to make that explicit
and in doing so begin to see how to adjust the policy enforcement/creation.
This relates to forem/forem#16913
While exploring the DashboardsController, I came across method calls to
`not_found`. Idiomatically, I assumed that these methods were returning
a value. However, in looking at the code, it raises an exception.
_Note: I excpect methods that raise exceptions, especially as the only
thing they do, to end in a `!`._
By adding the documentation my "IntelliSense" provides insight into the
expected behavior of this function (e.g. "Raises an exception").
Without the documentation, I don't see any useful information.
* prefer case to multiple if branches
case klass works fine on inheritance chains (we don't need to match on
inheritance explicitly).
* Verify case on error class behaves as before
* Fix tests
I don't know how I committed tests that were failing, but don't do that