DEV: Add CI setup and fix linting issues (#7)

This commit is contained in:
Jarek Radosz 2022-06-18 16:52:34 +02:00 committed by GitHub
parent 8ad475a960
commit 02e70aad1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 2273 additions and 78 deletions

8
.eslintrc Normal file
View File

@ -0,0 +1,8 @@
{
"extends": "eslint-config-discourse",
"ignorePatterns": ["javascripts/vendor/*"],
"globals": {
"settings": "readonly",
"themePrefix": "readonly"
}
}

48
.github/workflows/component-linting.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Linting
on:
push:
branches:
- main
pull_request:
concurrency:
group: plugin-linting-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16
cache: yarn
- name: Yarn install
run: yarn install
- name: ESLint
if: ${{ always() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,javascripts}
- name: Prettier
if: ${{ always() }}
shell: bash
run: |
yarn prettier -v
files=$(find javascripts desktop mobile common scss -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null) || true
if [ -n "$files" ]; then
yarn prettier --list-different $files
fi
if [ 0 -lt $(find test -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "test/**/*.{js,es6}"
fi
- name: Ember template lint
if: ${{ always() }}
run: yarn ember-template-lint --no-error-on-unmatched-pattern javascripts

147
.github/workflows/component-tests.yml vendored Normal file
View File

@ -0,0 +1,147 @@
name: Tests
on:
push:
branches:
- main
pull_request:
concurrency:
group: plugin-tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
outputs:
tests_exist: ${{ steps.check_tests.outputs.tests_exist }}
steps:
- name: Install component
uses: actions/checkout@v3
with:
path: tmp/component
fetch-depth: 1
- name: Check QUnit existence
id: check_tests
shell: bash
run: |
if [ 0 -lt $(find tmp/component/test -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
echo "::set-output name=tests_exist::true"
fi
test:
needs: check
if: ${{ needs.check.outputs.tests_exist }}
runs-on: ubuntu-latest
container: discourse/discourse_test:slim-browsers
timeout-minutes: 15
env:
DISCOURSE_HOSTNAME: www.example.com
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: development
PGUSER: discourse
PGPASSWORD: discourse
steps:
- uses: actions/checkout@v3
with:
repository: discourse/discourse
fetch-depth: 1
- name: Install component
uses: actions/checkout@v3
with:
path: tmp/component
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "ci@ci.invalid"
git config --global user.name "Discourse CI"
- name: Start redis
run: |
redis-server /etc/redis/redis.conf &
- name: Start Postgres
run: |
chown -R postgres /var/run/postgresql
sudo -E -u postgres script/start_test_db.rb
sudo -u postgres psql -c "CREATE ROLE $PGUSER LOGIN SUPERUSER PASSWORD '$PGPASSWORD';"
- name: Bundler cache
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
- name: Setup gems
run: |
gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock)
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean
- name: Lint English locale
run: bundle exec ruby script/i18n_lint.rb "tmp/component/locales/en.yml"
- name: Get yarn cache directory
id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Yarn install
run: yarn install
- name: Fetch app state cache
uses: actions/cache@v3
id: app-cache
with:
path: tmp/app-cache
key: >-
${{ hashFiles('.github/workflows/tests.yml') }}-
${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}-
- name: Restore database from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: psql -f tmp/app-cache/cache.sql postgres
- name: Restore uploads from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: rm -rf public/uploads && cp -r tmp/app-cache/uploads public/uploads
- name: Create and migrate database
if: steps.app-cache.outputs.cache-hit != 'true'
run: |
bin/rake db:create
bin/rake db:migrate
- name: Dump database for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: mkdir -p tmp/app-cache && pg_dumpall > tmp/app-cache/cache.sql
- name: Dump uploads for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads
- name: Component QUnit
run: |
THEME_NAME=$(ruby -e 'require "json"; puts JSON.parse(File.read("tmp/component/about.json"))["name"]')
bundle exec rake themes:install -- "--{\"$THEME_NAME\": \"tmp/component\"}"
UNICORN_TIMEOUT=120 bundle exec rake "themes:qunit[name,$THEME_NAME]"
timeout-minutes: 10

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
node_modules
.discourse-site .discourse-site
HELP

1
.prettierrc Normal file
View File

@ -0,0 +1 @@
{}

4
.template-lintrc.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
plugins: ["ember-template-lint-plugin-discourse"],
extends: "discourse:recommended",
};

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Discourse
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,14 +1,12 @@
{ {
"name": "User Card Directory", "name": "User Card Directory",
"component": true,
"about_url": "https://meta.discourse.org/t/user-card-directory/144479", "about_url": "https://meta.discourse.org/t/user-card-directory/144479",
"license_url": null, "license_url": null,
"authors": "David Taylor", "authors": "David Taylor",
"minimum_discourse_version": "2.5.0.beta2", "minimum_discourse_version": "2.5.0.beta2",
"assets": { "assets": {},
}, "color_schemes": {},
"color_schemes": {
},
"component": true,
"modifiers": { "modifiers": {
"svg_icons": ["id-card", "th-list"] "svg_icons": ["id-card", "th-list"]
} }

View File

@ -27,14 +27,14 @@
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
} }
.user-card-container{ .user-card-container {
margin: 60px 20px; margin: 60px 20px;
} }
@supports (display: grid) { @supports (display: grid) {
display: grid; display: grid;
.user-card-container{ .user-card-container {
margin: 0; margin: 0;
} }
} }

View File

@ -7,5 +7,5 @@ export default UserCardContents.extend({
cleanUp() {}, cleanUp() {},
didInsertElement() {}, didInsertElement() {},
willDestroyElement() {}, willDestroyElement() {},
keyUp() {} keyUp() {},
}); });

View File

@ -6,10 +6,10 @@ import { ajax } from "discourse/lib/ajax";
export default { export default {
name: "user-card-directory", name: "user-card-directory",
initialize(container) { initialize() {
withPluginApi("0.8.7", (api) => { withPluginApi("0.8.7", (api) => {
api.modifyClass("route:users", { api.modifyClass("route:users", {
pluginId: 'user-card-directory', pluginId: "user-card-directory",
resetController(controller, isExiting) { resetController(controller, isExiting) {
this._super(...arguments); this._super(...arguments);
if (isExiting) { if (isExiting) {
@ -47,33 +47,34 @@ export default {
}); });
api.modifyClass("controller:users", { api.modifyClass("controller:users", {
pluginId: 'user-card-directory', pluginId: "user-card-directory",
cachedUserCardInfo: null, cachedUserCardInfo: null,
init(){ init() {
this.set("cachedUserCardInfo", {}); this.set("cachedUserCardInfo", {});
this._super(...arguments); this._super(...arguments);
}, },
@discourseComputed("model.content.@each") @discourseComputed("model.content.@each")
userCards(allUsers) { userCards(allUsers) {
if (!allUsers) return []; if (!allUsers) {
return [];
}
const toLoad = []; const toLoad = [];
if (settings.hide_current_user && this.currentUser) { if (settings.hide_current_user && this.currentUser) {
allUsers = allUsers.filter((u) => u.id !== this.currentUser.id); allUsers = allUsers.filter((u) => u.id !== this.currentUser.id);
} }
const userCardInfos = allUsers.map(u => { const userCardInfos = allUsers.map((u) => {
if (this.cachedUserCardInfo[u.id]) { if (this.cachedUserCardInfo[u.id]) {
return this.cachedUserCardInfo[u.id]; return this.cachedUserCardInfo[u.id];
} }
const userCardInfo = (this.cachedUserCardInfo[ const userCardInfo = (this.cachedUserCardInfo[u.id] =
u.id EmberObject.create({
] = EmberObject.create({ user: User.create(u.user),
user: User.create(u.user), directoryItem: u,
directoryItem: u, loading: true,
loading: true }));
}));
toLoad.push(userCardInfo); toLoad.push(userCardInfo);
@ -85,17 +86,17 @@ export default {
while (toLoad.length > 0) { while (toLoad.length > 0) {
const thisBatch = toLoad.splice(0, loadMax); const thisBatch = toLoad.splice(0, loadMax);
const promise = ajax("/user-cards.json", { const promise = ajax("/user-cards.json", {
data: { user_ids: thisBatch.map(uc => uc.user.id).join(",") } data: { user_ids: thisBatch.map((uc) => uc.user.id).join(",") },
}); });
thisBatch.forEach(uc => { thisBatch.forEach((uc) => {
// Each user card expects its own promise // Each user card expects its own promise
// Rather than making a separate AJAX request for each // Rather than making a separate AJAX request for each
// We re-use the `user-cards.json` promise, and manipulate the data // We re-use the `user-cards.json` promise, and manipulate the data
const convertedPromise = promise.then(data => { const convertedPromise = promise.then((data) => {
// Find the correct user from users, and put it in the user attribute // Find the correct user from users, and put it in the user attribute
// Use Object.assign to avoid contaminating the source object // Use Object.assign to avoid contaminating the source object
return Object.assign({}, data, { return Object.assign({}, data, {
user: data.users.find(u => u.id === uc.user.id) user: data.users.find((u) => u.id === uc.user.id),
}); });
}); });
return uc.user return uc.user
@ -105,8 +106,8 @@ export default {
} }
return userCardInfos; return userCardInfos;
} },
}); });
}); });
} },
}; };

View File

@ -1,11 +1,22 @@
{{#d-section pageClass="users"}} {{#d-section pageClass="users"}}
{{#load-more selector=".user-card-directory .user-card-container" action=(action "loadMore")}} {{#load-more
selector=".user-card-directory .user-card-container"
action=(action "loadMore")
}}
<div class="container"> <div class="container">
<div class="users-directory directory"> <div class="users-directory directory">
{{plugin-outlet name="users-top" connectorTagName="div" args=(hash model=model)}} {{plugin-outlet
name="users-top"
connectorTagName="div"
args=(hash model=model)
}}
<div class="directory-controls"> <div class="directory-controls">
<div class="period-controls"> <div class="period-controls">
{{period-chooser period=period onChange=(action (mut period)) fullDay=false}} {{period-chooser
period=period
onChange=(action (mut period))
fullDay=false
}}
{{#if lastUpdatedAt}} {{#if lastUpdatedAt}}
<div class="directory-last-updated"> <div class="directory-last-updated">
{{i18n "directory.last_updated"}} {{i18n "directory.last_updated"}}
@ -31,9 +42,7 @@
value=group value=group
content=groupOptions content=groupOptions
onChange=(action groupChanged) onChange=(action groupChanged)
options=(hash options=(hash none="directory.group.all")
none="directory.group.all"
)
}} }}
{{/if}} {{/if}}
{{#if currentUser.staff}} {{#if currentUser.staff}}
@ -43,49 +52,64 @@
class="btn-default open-edit-columns-btn" class="btn-default open-edit-columns-btn"
}} }}
{{/if}} {{/if}}
{{plugin-outlet name="users-directory-controls" connectorTagName="" tagName="" args=(hash model=model)}} {{plugin-outlet
name="users-directory-controls"
connectorTagName=""
tagName=""
args=(hash model=model)
}}
</div> </div>
</div> </div>
{{#conditional-loading-spinner condition=isLoading}} {{#conditional-loading-spinner condition=isLoading}}
{{#if userCards.length}} {{#if userCards.length}}
<div class="user-card-directory"> <div class="user-card-directory">
{{#each userCards as |userCard|}} {{#each userCards as |userCard|}}
<div class="user-card-container"> <div class="user-card-container">
{{user-card-static {{user-card-static
user=userCard.user user=userCard.user
visible=true visible=true
loading=userCard.loading loading=userCard.loading
username=userCard.user.username username=userCard.user.username
}} }}
{{#if (theme-setting 'show_stats')}} {{#if (theme-setting "show_stats")}}
<div class='user-card-directory-footer'> <div class="user-card-directory-footer">
{{#each columns as |column|}} {{#each columns as |column|}}
<span class="stat stat-{{stat.name}}"> <span class="stat stat-{{stat.name}}">
<span class="value"> <span class="value">
{{#if (directory-column-is-user-field column=column)}} {{#if
{{directory-item-user-field-value item=userCard.directoryItem column=column}} (directory-column-is-user-field column=column)
{{else}} }}
{{directory-item-value item=userCard.directoryItem column=column}} {{directory-item-user-field-value
{{/if}} item=userCard.directoryItem
</span> column=column
<span class="label"> }}
{{table-header-toggle {{else}}
field=column.name {{directory-item-value
icon=column.icon item=userCard.directoryItem
order=order column=column
asc=asc }}
automatic=(directory-column-is-automatic column=column) {{/if}}
translated=column.user_field_id </span>
onActiveRender=setActiveHeader <span class="label">
}} {{table-header-toggle
</span> field=column.name
</span> icon=column.icon
{{/each}} order=order
asc=asc
automatic=(directory-column-is-automatic
column=column
)
translated=column.user_field_id
onActiveRender=setActiveHeader
}}
</span>
</span>
{{/each}}
</div>
{{/if}}
</div> </div>
{{/if}} {{/each}}
</div>
{{/each}}
</div> </div>
{{conditional-loading-spinner condition=model.loadingMore}} {{conditional-loading-spinner condition=model.loadingMore}}
{{else}} {{else}}

10
package.json Normal file
View File

@ -0,0 +1,10 @@
{
"name": "discourse-user-card-directory",
"version": "1.0.0",
"repository": "https://github.com/discourse/discourse-user-card-directory",
"author": "Discourse",
"license": "MIT",
"devDependencies": {
"eslint-config-discourse": "^3.2.0"
}
}

View File

@ -1,8 +1,4 @@
import { import { acceptance, count } from "discourse/tests/helpers/qunit-helpers";
acceptance,
exists,
count,
} from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit"; import { test } from "qunit";
import { click, visit } from "@ember/test-helpers"; import { click, visit } from "@ember/test-helpers";

1937
yarn.lock Normal file

File diff suppressed because it is too large Load Diff