Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #5015: Added CI to oppia-android wiki (check toc) #5382

Open
wants to merge 24 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
13efcfa
Script to Check the Table of Contents in the Wiki and CI workflow
Rd4dev Apr 10, 2024
d3795b3
Fix Lint Tests by adding new lines after and before ( and )
Rd4dev Apr 10, 2024
1840f60
Fix Lint Tests by properly indenting the error messages
Rd4dev Apr 10, 2024
9fdea36
Merge branch 'develop' of https://github.com/Rd4dev/oppia-android int…
Rd4dev Apr 29, 2024
d113747
Modified id with camel case convention and Revised the name to be mor…
Rd4dev Apr 29, 2024
39f0c2f
Merge branch 'develop' of https://github.com/Rd4dev/oppia-android int…
Rd4dev Jun 4, 2024
d55ddac
Resolve merge conflicts and fixed kdoc styling
Rd4dev Jul 4, 2024
49917c7
Renaming wiki dir variables for better clarity
Rd4dev Jul 5, 2024
94fa4f6
Simplified the wiki table of content check logic with kotlin functions
Rd4dev Jul 5, 2024
3e74a99
Code clean up and replaced exception with error statement
Rd4dev Jul 5, 2024
5a8b4b9
Added test cases for wiki toc checks
Rd4dev Jul 5, 2024
9e4386a
Fix Lint Checks for indentation and missing spaces
Rd4dev Jul 5, 2024
9193d15
Fix Lint check buildifier reformat
Rd4dev Jul 5, 2024
620c3b0
Merge branch 'develop' of https://github.com/Rd4dev/oppia-android int…
Rd4dev Sep 4, 2024
99c0371
Merge branch 'develop' of https://github.com/Rd4dev/oppia-android int…
Rd4dev Sep 5, 2024
72d0a2b
Added missing test cases for no directory found to hit 100% coverage
Rd4dev Sep 5, 2024
b5c9cf3
Upgraded the Bazel version, removed cache env, used Regex to check he…
Rd4dev Oct 2, 2024
fab6c2e
Merge branch 'develop' of https://github.com/Rd4dev/oppia-android int…
Rd4dev Oct 2, 2024
afaf59e
Added pull-request trigger to check on every PR creation, removed deb…
Rd4dev Oct 3, 2024
6dfa3bb
Merge branch 'develop' of https://github.com/Rd4dev/oppia-android int…
Rd4dev Oct 3, 2024
be00151
Trigger only when changes are made to the wiki dir
Rd4dev Oct 3, 2024
c18ac91
Conditional execution of the wiki-deploy to only proceed if the event…
Rd4dev Oct 3, 2024
2dfd045
Removed the test case for checking TOC present as the log statement t…
Rd4dev Oct 3, 2024
c7d0d11
Re-adding the test case to hit coverage but now checking with final r…
Rd4dev Oct 3, 2024
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
21 changes: 20 additions & 1 deletion .github/workflows/wiki.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,30 @@ on:
- develop
paths:
- 'wiki/**'
# Triggers this workflow when the wiki is changed
# Triggers this workflow when the wiki is changed.
# (see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum).
gollum:

jobs:
table_of_contents_check:
# To verify that the wiki's table of contents matches the headers accurately.
name: Check Wiki Table of Contents
runs-on: ubuntu-20.04
env:
CACHE_DIRECTORY: ~/.bazel_cache
adhiamboperes marked this conversation as resolved.
Show resolved Hide resolved
steps:
- uses: actions/checkout@v2

- name: Set up Bazel
uses: abhinavsingh/setup-bazel@v3
with:
version: 4.0.0

adhiamboperes marked this conversation as resolved.
Show resolved Hide resolved
- name: Check Wiki Table of Contents
id: checkWikiToc
run: |
bazel run //scripts:wiki_table_of_contents_check -- ${GITHUB_WORKSPACE}

wiki-deploy:
runs-on: ${{ matrix.os }}
strategy:
Expand Down
9 changes: 9 additions & 0 deletions scripts/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ kt_jvm_binary(
],
)

kt_jvm_binary(
name = "wiki_table_of_contents_check",
testonly = True,
main_class = "org.oppia.android.scripts.wiki.WikiTableOfContentsCheckKt",
runtime_deps = [
"//scripts/src/java/org/oppia/android/scripts/wiki:wiki_table_of_contents_check_lib",
],
)

kt_jvm_binary(
name = "run_coverage",
testonly = True,
Expand Down
18 changes: 18 additions & 0 deletions scripts/src/java/org/oppia/android/scripts/wiki/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
Libraries corresponding to scripting tools that help with continuous integration workflows.
"""

load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library")

kt_jvm_library(
name = "wiki_table_of_contents_check_lib",
testonly = True,
srcs = [
"WikiTableOfContentsCheck.kt",
],
visibility = ["//scripts:oppia_script_binary_visibility"],
deps = [
"//scripts/src/java/org/oppia/android/scripts/common:bazel_client",
"//scripts/src/java/org/oppia/android/scripts/common:git_client",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.oppia.android.scripts.wiki

import java.io.File

/**
* Script for ensuring that the table of contents in each wiki page matches with its respective headers.
*
* Usage:
* bazel run //scripts:wiki_table_of_contents_check -- <path_to_default_working_directory>
*
* Arguments:
* - path_to_default_working_directory: The default working directory on the runner for steps, and the default location of repository.
*
* Example:
* bazel run //scripts:wiki_table_of_contents_check -- $(pwd)
*/
fun main(vararg args: String) {
// Path to the repo's wiki.
val wikiDirPath = "${args[0]}/wiki/"
val wikiDir = File(wikiDirPath)

// Check if the wiki directory exists.
if (wikiDir.exists() && wikiDir.isDirectory) {
processWikiDirectory(wikiDir)
println("WIKI TABLE OF CONTENTS CHECK PASSED")
} else {
println("No contents found in the Wiki directory.")
}
}

/**
* Checks every file in the wiki repo.
*
* @param wikiDir the default working directory
*/
fun processWikiDirectory(wikiDir: File) {
wikiDir.listFiles()?.forEach { file ->
checkTableOfContents(file)
}
}

/**
* Checks the contents of a single wiki file to ensure the accuracy of the Table of Contents.
*
* @param file the wiki file to process.
*/
fun checkTableOfContents(file: File) {
val fileContents = file.readLines()
val tocStartIdx = fileContents.indexOfFirst {
it.contains("## Table of Contents")
adhiamboperes marked this conversation as resolved.
Show resolved Hide resolved
}
if (tocStartIdx == -1) {
println("No Table of Contents found for the file $file")
return
}

// Skipping the blank line after the ## Table of Contents
val eOfIdx = fileContents.subList(tocStartIdx + 2, fileContents.size).indexOfFirst {
it.isBlank()
}
if (eOfIdx == -1) error("Table of Contents didn't end with a blank line")
adhiamboperes marked this conversation as resolved.
Show resolved Hide resolved

val tocSpecificLines = fileContents.subList(tocStartIdx, tocStartIdx + eOfIdx + 1)
println("Toc line: $tocSpecificLines")

for (line in tocSpecificLines) {
if (line.trimStart().startsWith("- [") && !line.contains("https://")) {
validateTableOfContents(file, line)
}
}
}

/**
* Validates the accuracy of a Table of Contents entry in a wiki file.
*
* @param file the wiki file being validated.
* @param line the line containing the Table of Contents entry.
*/
fun validateTableOfContents(file: File, line: String) {
val titleRegex = "\\[(.*?)\\]".toRegex()
val title = titleRegex.find(line)?.groupValues?.get(1)?.replace('-', ' ')
?.replace(Regex("[?&./:’'*!,(){}\\[\\]+]"), "")
?.trim()

val linkRegex = "\\(#(.*?)\\)".toRegex()
val link = linkRegex.find(line)?.groupValues?.get(1)?.removePrefix("#")?.replace('-', ' ')
?.replace(Regex("[?&./:’'*!,(){}\\[\\]+]"), "")
?.replace("confetti_ball", "")?.trim()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might we, instead, want to ban emojis? It would reduce the maintainance burden. WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have two thoughts on this but I'm unsure of the right approach:

  1. We could restrict using emojis (But would this require adding a check to ensure no emojis are included in headers, or would it just be a manual review check?).
  2. Alternatively, we could still accept emojis, but they should also be included in the TOC titles in the :emoji: form for comparison and validation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't emojis fail by default since their presence would cause a mismatch?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, got it. I thought we should implement something to restrict adding emojis separately while pushing. In that case, the current check could work in both ways - by checking the TOC and also by allowing contributors to add emojis (if needed) but in a certain pattern.

  • If an emoji is added as a visual emoji (standard image emoji) [🦦], the headers should ignore adding it. - [This is an option if contributors want to include emojis in their headers but don't want them in the TOC representation]
  • If an emoji is added as GitHub emoji code (:emoji:) [:otter:], the headers should include it. - [This is an option if contributors want to represent them in both headers and the TOC representation]

Few examples that would work are:

Table of Contents

- [Instructions1](#instructions1-)
- [Instructions2 :otter:](#instructions2-otter)
- [Instructions3](#-instructions3)
- [:otter: Instructions4](#otter-instructions4)

Headers

  • Instructions1 🦦
  • Instructions2 🦦
  • 🦦 Instructions3
  • 🦦 Instructions4
## Instructions1 🦦
## Instructions2 :otter:
## 🦦 Instructions3
## :otter: Instructions4

So, anything in the wrong format would be checked by the script, and contributors can decide how they want to represent it, I guess.


// Checks if the table of content title matches with the header link text.
val matches = title.equals(link, ignoreCase = true)
if (!matches) {
error(
"\nWIKI TABLE OF CONTENTS CHECK FAILED" +
"\nMismatch of Table of Content with headers in the File: ${file.name}. " +
"\nThe Title: '${titleRegex.find(line)?.groupValues?.get(1)}' " +
"doesn't match with its corresponding Link: '${linkRegex.find(line)?.groupValues?.get(1)}'."
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""
Tests corresponding to wiki-related checks.
"""

load("@io_bazel_rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test")

kt_jvm_test(
name = "WikiTableOfContentsCheckTest",
srcs = ["WikiTableOfContentsCheckTest.kt"],
deps = [
"//scripts/src/java/org/oppia/android/scripts/wiki:wiki_table_of_contents_check_lib",
"//testing:assertion_helpers",
"//third_party:com_google_truth_truth",
"//third_party:org_jetbrains_kotlin_kotlin-test-junit",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package org.oppia.android.scripts.wiki

import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.oppia.android.testing.assertThrows
import java.io.ByteArrayOutputStream
import java.io.PrintStream

/** Tests for [WikiTableOfContentsCheck]. */
class WikiTableOfContentsCheckTest {
private val outContent: ByteArrayOutputStream = ByteArrayOutputStream()
private val originalOut: PrintStream = System.out
private val WIKI_TOC_CHECK_PASSED_OUTPUT_INDICATOR = "WIKI TABLE OF CONTENTS CHECK PASSED"
private val WIKI_TOC_CHECK_FAILED_OUTPUT_INDICATOR = "WIKI TABLE OF CONTENTS CHECK FAILED"

@field:[Rule JvmField] val tempFolder = TemporaryFolder()

@Before
fun setUp() {
System.setOut(PrintStream(outContent))
}

@After
fun tearDown() {
System.setOut(originalOut)
}

@Test
fun testWikiTOCCheck_noWikiDirExists_printsNoContentFound() {
runScript()
assertThat(outContent.toString().trim()).isEqualTo("No contents found in the Wiki directory.")
}

@Test
fun testWikiTOCCheck_noWikiDirectory_printsNoContentFound() {
tempFolder.newFile("wiki")
runScript()
assertThat(outContent.toString().trim()).isEqualTo("No contents found in the Wiki directory.")
}

@Test
fun testWikiTOCCheck_validWikiTOC_checkPass() {
tempFolder.newFolder("wiki")
val file = tempFolder.newFile("wiki/wiki.md")
file.writeText(
"""
## Table of Contents

- [Introduction](#introduction)
- [Usage](#usage)

## Introduction
Content

## Usage
Content
""".trimIndent()
)

runScript()

assertThat(outContent.toString().trim()).contains(WIKI_TOC_CHECK_PASSED_OUTPUT_INDICATOR)
}

@Test
fun testWikiTOCCheck_missingWikiTOC_returnsNoTOCFound() {
tempFolder.newFolder("wiki")
val file = tempFolder.newFile("wiki/wiki.md")
file.writeText(
"""
- [Introduction](#introduction)
- [Usage](#usage)

## Introduction
Content

## Usage
Content
""".trimIndent()
)

runScript()

assertThat(outContent.toString().trim()).contains("No Table of Contents found")
}

@Test
fun testWikiTOCCheck_mismatchWikiTOC_checkFail() {
tempFolder.newFolder("wiki")
val file = tempFolder.newFile("wiki/wiki.md")
file.writeText(
"""
## Table of Contents

- [Introduction](#introductions)
- [Usage](#usage)

## Introduction
Content

## Usage
Content
""".trimIndent()
)

val exception = assertThrows<IllegalStateException>() {
runScript()
}

assertThat(exception).hasMessageThat().contains(WIKI_TOC_CHECK_FAILED_OUTPUT_INDICATOR)
}

@Test
fun testWikiTOCCheck_validWikiTOCWithSeparator_checkPass() {
tempFolder.newFolder("wiki")
val file = tempFolder.newFile("wiki/wiki.md")
file.writeText(
"""
## Table of Contents

- [Introduction To Wiki](#introduction-to-wiki)
- [Usage Wiki-Content](#usage-wiki-content)

## Introduction
Content

## Usage
Content
""".trimIndent()
)

runScript()

assertThat(outContent.toString().trim()).contains(WIKI_TOC_CHECK_PASSED_OUTPUT_INDICATOR)
}

@Test
fun testWikiTOCCheck_validWikiTOCWithSpecialCharacter_checkPass() {
tempFolder.newFolder("wiki")
val file = tempFolder.newFile("wiki/wiki.md")
file.writeText(
"""
## Table of Contents

- [Introduction](#introduction?)
- [Usage?](#usage)

## Introduction
Content

## Usage
Content
""".trimIndent()
)

runScript()

assertThat(outContent.toString().trim()).contains(WIKI_TOC_CHECK_PASSED_OUTPUT_INDICATOR)
}

private fun runScript() {
main(tempFolder.root.absolutePath)
}
}
Loading