Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
\.DS_Store
scripts/node_modules
scripts/learningpath
scripts/.env
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ Here is [an example](https://github.com/InnerSourceCommons/InnerSourceLearningPa
* Here is the [status of translation efforts](https://github.com/InnerSourceCommons/InnerSourceLearningPath/wiki/Translations).
* Other mechanics of working on translations follow the guidelines below that apply to any file in the `InnerSourceLearningPath`.

# Subtitles

* All videos may have subtitles in any language.
* See [this video](https://drive.google.com/file/d/1IaAH8Zmp2ggBtelexhaZUqia5yS8mUjE/view?usp=sharing) for how to add them.

# Files

* When submitting new files, follow the folder structure listed under **Repository Setup** in [the README](./README.md).
Expand Down
69 changes: 69 additions & 0 deletions contributor/ja/02-becoming-a-contributor-article-ja.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
== InnerSourceコントリビューターになる

InnerSourceのコントリビューターは、通常のチームの境界外で活動し、組織のサイロを横断するリンクになります。
そのため、彼らはこの作業をより効果あるものにするための、いくつかの共通の方法を意識する必要があります。

=== マインドセットの共有

それでは - あなたはチームの製品に、新しい機能を実装しています。
この機能を動作させるためには、いくつかの機能が必要です。
実装に直ぐに取り掛かる代わりに、少し落ち着いて考えます:この機能は一般的な課題なのだろうか?
それは、あなたの組織の他のチームが共有するビジネスドメインで同じように直面しているものなのか?
この機能は、あなたのプロジェクトのドメインと直交するものなのか?
もし当てはまる場合、自分のチームを超えて見渡して:あなたのニーズにフィットするために利用したり改善できる共通のソリューションがあるのか?
あるべきなのか?

=== ソリューションを共有するメリット

アフリカには、 "`早く行きたいなら一人で行きなさい。遠くに行きたいなら一緒に行きなさい`" という諺があります。これは、ソフトウェア開発チームにも同じです。

もし、早く進みたいのであれば、依存関係を壊すことは良いアイデアです。
この欠点としては、重複した作業になることです。
とりわけ、コアロジックを再実装する時には、重複することのコストが独立性のメリットを上回る、非常に現実的なリスクがあります。

他のチームとコラボレーションすることで、開発コストを共有できます。
オープンソースプロジェクトと同様に、あなたが一人で成し遂げられるより大きな何かを、あなたのチームが作ることを可能とします。

しかし、それぞれのチームには異なるロードマップがあります!
私は以前、共有のコンポーネントを使おうとしました - それらを再実装するよりも統合することに時間がかかりました。それらのコンポーネントは常に何か欠けてました。 - それに、私の機能リクエストは他のチームのロードマップで優先的になることはありませんでした!

従来のプロジェクトと比べると、InnerSourceのプロジェクトにはコントリビューターの役割があります。
あなたは共通のソリューションに新しい機能があればと思うこともあるでしょう。
コントリビューターとして、あなたにはソースコードをチェックアウトして変更を加え、改良したバージョンを利用する自由があります。

あなたのタイムラインで、ホストチームでは同じ優先度を持たないバグの修正が緊急に必要になることもあるでしょう。
コントリビューターになることで、あなた自身が行動し、ホストチームをバグ修正でサポートすることができるようになります。

この作業方法は、多くの点でマインドセットの変更が必要となります: 機能やバグの修正を待つ代わりに、問題を回避する代わりに、そこには第3の解決策があります。あなたの時間や労力を費やしてInnerSourceプロジェクトにおけるあなたのニーズを確認し - それから共有されたプロジェクトに直接変更を加えます。
そして、あなたにはコーディングスキルに加え、InnerSourceプロジェクトで成功するためのコミュニケーションスキルが必要です - あなたのニーズを明確化し、あなたのチームとホストチームの両方で機能するソリューションを実現するために。

"しかし、私はプロジェクトをフォークして、そこに変更を加え、この調整のオーバーヘッドから自身を守りました!"
確かに、プロジェクトをフォークすることは、あなたの仕事を終わらせる方法です。
長期的な場合を除いては、これはあなたのチームがフォークしたバージョンをメンテナンスし、ホストチームが作成する新しいリリースのいずれにも、その変更を追随させる必要があるということを意味します。
あなたの変更をホストチームにコントリビューションすることは、彼らのコンポーネントに関するより深い知識からメリットを得られるということも意味します。
彼らは、本番でしか明らかにならないようなパッチの問題を明らかにするかも知れません。

優れたコントリビュータは、作業を複製する代わりに依存関係を導入してコンポーネントを再利用することが技術的にもビジネス的にも意味がある時に、気軽に声をかけることができます。彼らは、InnerSouceコントリビューションのメリットを説明するために、マネージメントに話をすることができます。

=== InnerSourceコントリビューションのスコープ

では、Inner__Source__ とは、__ソース__コード だけのことなのでしょうか?
もちろん、違います。
もし、あなたのチームのビジネスが外部のコンポーネントに依存していたら、あなたはそれが適切にメンテナンスされ動作することを確認したいでしょう。
InnerSouceのコントリビューターとしては、あなたはホストチームを複数の方法で支援できます。
コンポーネントを利用している際の問題を報告することは、価値あるコントリビューションです。
コードが期待通りに動作していないことを示すテストケースを作成したり修正することは価値があります。
そして、ドキュメントを改善することは、他の人がそれを利用したりコントリビューションしたりするために費やす時間を少なくします。
他のユーザをサポートすること、バグのトリアージを支援することは、価値あるコントリビューションです。
ビルドの改善も、価値あるコントリビューションの例です。

要約すると、どのようなコントリビューションでも、小さすぎるということはありません。
これは私が https://github.com/tensorflow/models/pull/4784[tensorflow/models] に対して作成したものです。
単純なグラフのラベル変更です。

=== この記事のまとめ

この記事では、コントリビュータになるために必要なことについて学びました。
私達は共有のマインドセットを見ました。
ソリューションを共有することのメリットについて深く掘り下げました。
最後に、InnerSourceのコントリビューションのスコープが、一般的にどのようなものとなるかを見てみました。
7 changes: 6 additions & 1 deletion scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ node substitute_article_urls.js isc

A node script to generate markdown files required for hosting Learning Path on innersourcecommons.org.

This script requires a [GitHub access token](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token), as it uses the GitHub API to get Learning Path contributors. Your token does not require any scopes, as the Learning Path is Open Source. To provide this, create a `.env` file in this directory in the following format:
```
TOKEN=<your_github_token>
```

### Usage:
```
npm ci
Expand All @@ -36,7 +41,7 @@ If so, then do both of the following:
* update the ["sections" config](https://github.com/InnerSourceCommons/InnerSourceLearningPath/blob/master/scripts/generate_learning_path_markdown.js#L37) with the language code of the articles for the appropriate section.
Open a pull request for the change.
* update the [Learning Path landing page](https://github.com/InnerSourceCommons/innersourcecommons.org/blob/master/resources/learningpath/index.md) with a link to your new language pages.

3. Run **generate_learning_path_markdown.js** as described above.
3. `cp -r learningpath/* <path-to-innersourcecommons.org-repo>/resources/learningpath/`.
3. Open a pull request with the modified files in the [InnerSourceCommons/innersourcecommons.org] repo.
Expand Down
217 changes: 112 additions & 105 deletions scripts/generate_learning_path_markdown.js
Original file line number Diff line number Diff line change
@@ -1,120 +1,127 @@
const fs = require('fs')
const YAML = require('yaml')
const { EOL } = require('os')
const { join } = require('path')
(async() => {
const fs = require('fs')
const YAML = require('yaml')
const { EOL } = require('os')
const { join } = require('path')
const getContributors = require('./get_contributors')

const mkdirSync = (dir) => {
try {
fs.mkdirSync(dir)
} catch (e) {
if (e.code !== 'EEXIST') {
console.log(e)
const mkdirSync = (dir) => {
try {
fs.mkdirSync(dir)
} catch (e) {
if (e.code !== 'EEXIST') {
console.log(e)
}
}
}
}

const getArticleFiles = (path) => {
return fs.readdirSync(path).reduce((articles, filename) => {
const filePath = `${path}/${filename}`
if (filePath.match(/\d\d/) && !filePath.includes('-script.asciidoc')) {
return [...articles, {
filePath,
asciiDoc: fs.readFileSync(filePath, 'utf-8')
}]
} else {
return articles
}
}, [])
}
const getArticleFiles = (path) => {
return fs.readdirSync(path).reduce((articles, filename) => {
const filePath = `${path}/${filename}`
if (filePath.match(/\d\d/) && !filePath.includes('-script.asciidoc')) {
return [...articles, {
filePath,
asciiDoc: fs.readFileSync(filePath, 'utf-8')
}]
} else {
return articles
}
}, [])
}

const writeMarkdownFile = (filePath, frontMatter) => {
const frontMatterTerminator = '---'
const originStatement = '<!--- This file autogenerated from https://github.com/InnerSourceCommons/InnerSourceLearningPath/blob/master/scripts/generate_learning_path_markdown.js -->'
const output = [frontMatterTerminator, YAML.stringify(frontMatter).trim(), frontMatterTerminator, originStatement].join(EOL)
fs.writeFileSync(filePath, output)
}
const writeMarkdownFile = (filePath, frontMatter) => {
const frontMatterTerminator = '---'
const originStatement = '<!--- This file autogenerated from https://github.com/InnerSourceCommons/InnerSourceLearningPath/blob/master/scripts/generate_learning_path_markdown.js -->'
const output = [frontMatterTerminator, YAML.stringify(frontMatter).trim(), frontMatterTerminator, originStatement].join(EOL)
fs.writeFileSync(filePath, output)
}

const sections = [
{
learning_path_group: 'Introduction',
dirName: 'introduction',
workbook: '01-introduction.asciidoc',
translations: ['de', 'it', 'ja', 'zh'],
renderArticles: true
},
{
learning_path_group: 'Trusted Committer',
dirName: 'trusted-committer',
workbook: '02-trusted-committer.asciidoc',
translations: ['de', 'zh'],
renderArticles: true
},
{
learning_path_group: 'Contributor',
dirName: 'contributor',
workbook: '04-contributor.asciidoc',
translations: ['ja', 'zh'],
renderArticles: true
},
{
learning_path_group: 'Product Owner',
dirName: 'product-owner',
workbook: '03-product-owner.asciidoc',
translations: [],
renderArticles: false
},
]
const sections = [
{
learning_path_group: 'Introduction',
dirName: 'introduction',
workbook: '01-introduction.asciidoc',
translations: ['de', 'it', 'ja', 'zh'],
renderArticles: true
},
{
learning_path_group: 'Trusted Committer',
dirName: 'trusted-committer',
workbook: '02-trusted-committer.asciidoc',
translations: ['de', 'zh'],
renderArticles: true
},
{
learning_path_group: 'Contributor',
dirName: 'contributor',
workbook: '04-contributor.asciidoc',
translations: ['ja', 'zh'],
renderArticles: true
},
{
learning_path_group: 'Product Owner',
dirName: 'product-owner',
workbook: '03-product-owner.asciidoc',
translations: ['zh'],
renderArticles: true
},
]

mkdirSync('./learningpath')

sections.forEach(({ learning_path_group, dirName, workbook, translations, renderArticles }) => {
const baseReadPath = `../${dirName}`
const baseWritePath = `./learningpath/${dirName}`
mkdirSync(baseWritePath)
sections.forEach(({ learning_path_group, dirName, workbook, translations, renderArticles }) => {
const baseReadPath = `../${dirName}`
const baseWritePath = `./learningpath/${dirName}`
mkdirSync(baseWritePath)

translations.concat('' /* The English original */).forEach((translation) => {
const isTranslation = translation !== ''
const writePath = join(baseWritePath, translation)
mkdirSync(writePath)
translations.concat('' /* The English original */).forEach(async (translation) => {
const isTranslation = translation !== ''
const writePath = join(baseWritePath, translation)
mkdirSync(writePath)

const readPath = join(baseReadPath, translation)
const articles = getArticleFiles(readPath)
articles.forEach((article) => {
const articleTitle = article.asciiDoc.match(/== (.*)/)[1]
const articleNumber = article.filePath.split('/').pop().split('-')[0]
const fileName = articleNumber === '01' ? `${writePath}/index.md` : `${writePath}/${articleNumber}.md`
const frontMatter = {
layout: 'learning-path-page',
show_meta: false,
title: `Learning Path - ${learning_path_group} - ${articleTitle}`,
learning_path_article: renderArticles ? article.filePath.replace('../', '') : undefined,
learning_path_group,
learning_path_menu_title: `${articleNumber} - ${articleTitle}`,
learning_path_position: parseInt(articleNumber),
learning_path_translation: translation,
no_video: isTranslation // Videos not available translated.
}
const readPath = join(baseReadPath, translation)
const articles = getArticleFiles(readPath)
articles.forEach(async (article) => {
const articleTitle = article.asciiDoc.match(/== (.*)/)[1]
const articleNumber = article.filePath.split('/').pop().split('-')[0]
const fileName = articleNumber === '01' ? `${writePath}/index.md` : `${writePath}/${articleNumber}.md`
const contributors = await getContributors(article.filePath.replace('../', ''))
const frontMatter = {
layout: 'learning-path-page',
show_meta: false,
title: `Learning Path - ${learning_path_group} - ${articleTitle}`,
learning_path_article: renderArticles ? article.filePath.replace('../', '') : undefined,
learning_path_group,
learning_path_menu_title: `${articleNumber} - ${articleTitle}`,
learning_path_position: parseInt(articleNumber),
learning_path_translation: translation,
no_video: isTranslation, // Videos not available translated.
contributors
}

writeMarkdownFile(fileName, frontMatter)
})
writeMarkdownFile(fileName, frontMatter)
})

// Workbooks not translated.
if (!isTranslation) {
const workbookFileName = `${writePath}/workbook.md`
console.log('workbookFileName', workbookFileName)
const workbookFrontMatter = {
layout: 'learning-path-page',
show_meta: false,
title: `Learning Path - ${learning_path_group} - Workbook`,
learning_path_article: `workbook/${workbook}`,
learning_path_group,
learning_path_menu_title: `${learning_path_group} Workbook`,
learning_path_position: articles.length - articles.filter(Array.isArray).length + 1,
learning_path_translation: translation,
no_video: true
}
// Workbooks not translated.
if (!isTranslation) {
const workbookFileName = `${writePath}/workbook.md`
const contributors = await getContributors(`workbook/${workbook}`)
console.log('workbookFileName', workbookFileName)
const workbookFrontMatter = {
layout: 'learning-path-page',
show_meta: false,
title: `Learning Path - ${learning_path_group} - Workbook`,
learning_path_article: `workbook/${workbook}`,
learning_path_group,
learning_path_menu_title: `${learning_path_group} Workbook`,
learning_path_position: articles.length - articles.filter(Array.isArray).length + 1,
learning_path_translation: translation,
no_video: true,
contributors
}

writeMarkdownFile(workbookFileName, workbookFrontMatter)
}
writeMarkdownFile(workbookFileName, workbookFrontMatter)
}
})
})
})
})()
48 changes: 48 additions & 0 deletions scripts/get_contributors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require('dotenv').config()
const { graphql } = require("@octokit/graphql")

const graphqlWithAuth = graphql.defaults({
headers: {
authorization: `token ${process.env.TOKEN}`
}
})

module.exports = async function (filepath) {
const contributors = await graphqlWithAuth(
`{
repository(owner: "InnerSourceCommons", name: "InnerSourceLearningPath") {
object(expression: "master") {
... on Commit {
history(first: 100, path: "${filepath}") {
totalCount
nodes {
author {
name
user {
name
url
}
}
}
}
}
}
}
}`
)

const history = contributors.repository.object.history

if (history.totalCount > 100) {
throw Error('This script needs updating to handle >100 commits')
}

return Object.values(history.nodes.reduce((acc, { author }) => {
const name = (author.user && author.user.name) || author.name
acc[name] = {
name,
url: author.user && author.user.url
}
return acc
}, {}))
}
Loading