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

This commit is contained in:
Jarek Radosz 2022-06-18 21:24:57 +02:00 committed by GitHub
parent 3d341a4735
commit 1e7381dc64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 2195 additions and 27 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 Normal file
View File

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

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",
};

View File

@ -1,6 +1,5 @@
{ {
"name": "Header submenus", "name": "Header submenus",
"about_url": "https://meta.discourse.org/t/", "component": true,
"license_url": "https://github.com/discourse/discourse-header-submenus/blob/main/LICENSE", "license_url": "https://github.com/discourse/discourse-header-submenus/blob/main/LICENSE"
"component": true
} }

View File

@ -1,28 +1,40 @@
<div id='top-menu' class='top-menu'> <div id="top-menu" class="top-menu">
<div class='menu-content wrap'> <div class="menu-content wrap">
<div class="menu-placeholder"> <div class="menu-placeholder">
<div class="menu-item-container"> <div class="menu-item-container">
<div class="menu-items"> <div class="menu-items">
{{#each menuItems as |item|}} {{#each menuItems as |item|}}
<a class="menu-item {{item.view}} {{item.className}}" title="{{item.title}}"> <a
class="menu-item {{item.view}} {{item.className}}"
title={{item.title}}
>
{{#if item.icon}} {{#if item.icon}}
{{d-icon item.icon}} {{d-icon item.icon}}
{{/if}} {{/if}}
{{item.text}} {{item.text}}
{{#if showCaret}} {{#if showCaret}}
{{d-icon "caret-right"}} {{d-icon "caret-right"}}
{{/if}} {{/if}}
<div class="d-header-dropdown"> <div class="d-header-dropdown">
<ul class="d-dropdown-menu"> <ul class="d-dropdown-menu">
{{#each item.childItems as |child|}} {{#each item.childItems as |child|}}
{{#if child.divider}} {{#if child.divider}}
<li class='divider'></li> <li class="divider"></li>
{{else}} {{else}}
<li class="submenu-item {{child.className}}"> <li class="submenu-item {{child.className}}">
<a target="{{child.target}}" title="{{child.title}}" class="submenu-link" href="{{child.href}}"> <a
target={{child.target}}
title={{child.title}}
class="submenu-link"
href={{child.href}}
>
{{#if child.icon}} {{#if child.icon}}
{{d-icon child.icon}} {{d-icon child.icon}}
{{/if}} {{/if}}
{{child.text}} {{child.text}}
</a> </a>
</li> </li>

View File

@ -1,28 +1,27 @@
// Used instead of dasherize for backwards compatibility with stable // Used instead of dasherize for backwards compatibility with stable
const getClassName = text => { const getClassName = (text) => {
return text.toLowerCase().replace(/\s/g, "-"); return text.toLowerCase().replace(/\s/g, "-");
}; };
export default { export default {
setupComponent(args, component) { setupComponent() {
try { try {
const splitMenuItems = settings.Menu_items.split("|").filter(Boolean); const splitMenuItems = settings.Menu_items.split("|").filter(Boolean);
const splitSubmenuItems = settings.Submenu_items.split("|").filter( const splitSubmenuItems =
Boolean settings.Submenu_items.split("|").filter(Boolean);
);
const menuItemsArray = []; const menuItemsArray = [];
const SubmenuItemsArray = []; const SubmenuItemsArray = [];
splitSubmenuItems.forEach(item => { splitSubmenuItems.forEach((item) => {
const fragments = item.split(",").map(fragment => fragment.trim()); const fragments = item.split(",").map((fragment) => fragment.trim());
const parent = fragments[0].toLowerCase(); const parent = fragments[0].toLowerCase();
const text = fragments[1]; const text = fragments[1];
if (text.toLowerCase() === "divider") { if (text.toLowerCase() === "divider") {
const divider = { const divider = {
parent, parent,
divider: true divider: true,
}; };
return SubmenuItemsArray.push(divider); return SubmenuItemsArray.push(divider);
} }
@ -36,20 +35,20 @@ export default {
const target = fragments[4] === "blank" ? "_blank" : ""; const target = fragments[4] === "blank" ? "_blank" : "";
const title = fragments[5]; const title = fragments[5];
const submenItem = { const submenuItem = {
parent, parent,
text, text,
className, className,
icon, icon,
href, href,
target, target,
title title,
}; };
SubmenuItemsArray.push(submenItem); SubmenuItemsArray.push(submenuItem);
}); });
splitMenuItems.forEach(item => { splitMenuItems.forEach((item) => {
const fragments = item.split(",").map(fragment => fragment.trim()); const fragments = item.split(",").map((fragment) => fragment.trim());
const parentFor = fragments[0].toLowerCase(); const parentFor = fragments[0].toLowerCase();
const text = fragments[0]; const text = fragments[0];
const className = getClassName(text); const className = getClassName(text);
@ -60,7 +59,7 @@ export default {
const title = fragments[2]; const title = fragments[2];
const view = fragments[3]; const view = fragments[3];
const childItems = SubmenuItemsArray.filter( const childItems = SubmenuItemsArray.filter(
link => link.parent === parentFor (link) => link.parent === parentFor
); );
const menuItem = { const menuItem = {
@ -69,7 +68,7 @@ export default {
icon, icon,
title, title,
view, view,
childItems childItems,
}; };
menuItemsArray.push(menuItem); menuItemsArray.push(menuItem);
}); });
@ -78,13 +77,14 @@ export default {
this.setProperties({ this.setProperties({
menuItems: menuItemsArray, menuItems: menuItemsArray,
showCaret showCaret,
}); });
} catch (error) { } catch (error) {
console.error(error); // eslint-disable-next-line no-console
console.error( console.error(
error,
"There's an issue in the Header Submenus Component. Check if your settings are entered correctly" "There's an issue in the Header Submenus Component. Check if your settings are entered correctly"
); );
} }
} },
}; };

10
package.json Normal file
View File

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

1937
yarn.lock Normal file

File diff suppressed because it is too large Load Diff