Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
name: Playwright
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.54.0-noble
image: mcr.microsoft.com/playwright:v1.58.2-noble
options: --user 1001
steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=green-code-initiative_creedengo-dashboard&metric=coverage)](https://sonarcloud.io/summary/new_code?id=green-code-initiative_creedengo-dashboard)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=green-code-initiative_creedengo-dashboard&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=green-code-initiative_creedengo-dashboard)

This project is meant to provide Sustainable Code Dashboards to
This project is meant to provide Sustainable Code Dashboard to

- show potential impact on sustainability of unffollowed recommendations
- help decisions regarding code enhancement priorisation
Expand Down
21 changes: 21 additions & 0 deletions apps/vscode-sonar-extension/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
Comment on lines +1 to +4

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue: We cannot include comments this way in a JSON File

A raw option could be to add a "_comments" array property at the root of the json object with these 2 lines as string items (not recommended)

A better way would be to

  • add a README.md file in the .vscode folder with such tips
  • add a json schema link property "$schema": "vscode://schemas/launch"
    • this vscode schema is dynamic which is why this link is very local

{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/dist/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
}
]
}
33 changes: 18 additions & 15 deletions apps/vscode-sonar-extension/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
Comment on lines +1 to +2

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue: We cannot include comments this way in a JSON File

A raw option could be to add a "_comments" array property at the root of the json object with these 2 lines as string items (not recommended)

A better way would be to

  • add a README.md file in the .vscode folder with such tips
  • add a json schema link property "$schema": "vscode://schemas/tasks"

{
"version": "2.0.0",
"tasks": [
{
"label": "watch",
"type": "npm",
"script": "watch",
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
]
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
8 changes: 8 additions & 0 deletions apps/vscode-sonar-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@
{
"command": "creedengo.updateScore",
"title": "Update Creedengo Sustainability Score"
},
{
"command": "creedengo.updatePriorityRule",
"title": "Update Creedengo Priority Rule"
},
{
"command": "creedengo.updateFootPrint",
"title": "Update Creedengo Footprint"
}
],
"views": {
Expand Down
6 changes: 6 additions & 0 deletions apps/vscode-sonar-extension/src/extension.mjs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

todo: as suggested by Sonar, group the subscriptions.push()

subscriptions.push(
  commandRegistration1,
  commandRegistration2,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion(non-blocking): extension.mjs files should be unit tested

Non blocking only because there no vscode unit tests yet
But highly recommended

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@
subscriptions.push(
commands.registerCommand('creedengo.updateScore', () => provider.updateScore())
);
subscriptions.push(
commands.registerCommand('creedengo.updatePriorityRule', () => provider.updatePriorityRule())
);
subscriptions.push(

Check warning on line 26 in apps/vscode-sonar-extension/src/extension.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not call `Array#push()` multiple times.

See more on https://sonarcloud.io/project/issues?id=green-code-initiative_creedengo-dashboard&issues=AZ6SYGJM_-3rCImSNDFG&open=AZ6SYGJM_-3rCImSNDFG&pullRequest=201
commands.registerCommand('creedengo.updateFootPrint', () => provider.updateFootPrint())
);
subscriptions.push(

Check warning on line 29 in apps/vscode-sonar-extension/src/extension.mjs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not call `Array#push()` multiple times.

See more on https://sonarcloud.io/project/issues?id=green-code-initiative_creedengo-dashboard&issues=AZ6SYGJM_-3rCImSNDFH&open=AZ6SYGJM_-3rCImSNDFH&pullRequest=201
commands.registerCommand('creedengo.refresh', () => provider.refresh())
);

Expand Down

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion(non-blocking): configuration.service.mjs files should be unit tested

Non blocking only because there no vscode unit tests yet
But highly recommended

Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function readCreedengoVsCodeConfiguration() {
/**
* @returns {Promise<CreedengoAuthSettings>}
*/
async function getSonarConfiguration() {
export async function getSonarConfiguration() {
const configuration = readCreedengoVsCodeConfiguration()
const properties = await readSonarProjectProperties()
Object.assign(configuration, properties)
Expand Down
38 changes: 32 additions & 6 deletions apps/vscode-sonar-extension/src/views/score/score.script.mjs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion(non-blocking): score.script.mjs files should be unit tested

Non blocking only because there no vscode unit tests yet
But highly recommended

Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@ import { addMessageHandler } from '../../services/message.service.mjs'
let errorCount = 0;

const scoreOutput = document.querySelector('.score');
const priorityRuleNameElem = document.querySelector('.priority-rule');
const footPrintElem = document.querySelector('.footprint');
const branchFields = document.forms['branch-selection'].elements;
const branchesSelect = branchFields['branch'];

try {
const oldState = vscode.getState() || { score: '', branch: '' };
let { score, branch } = oldState;
const oldState = vscode.getState() || { score: '', priorityRule: '', footPrint: '', branch: '' };
let { score, priorityRule, footPrint, branch } = oldState;

const messageHandler = addMessageHandler({
updateScore: data => updateScore(data.value),
updateBranches: data => updateBranches(data.value),
updatePriorityRule: data => updatePriorityRule(data.value),
updateFootPrint: data => updateFootPrint(data.value),
clearScore: () => updateScore(''),
}, logError)

Expand All @@ -32,8 +36,30 @@ import { addMessageHandler } from '../../services/message.service.mjs'
*/
function updateScore(newScore) {
score = newScore;
scoreOutput.textContent = `Score: ${score || 'Cleared'}`;
vscode.setState({ score, branch });
scoreOutput.textContent = `${score || 'Cleared'}`;
vscode.setState({ score, priorityRule, footPrint, branch });
}

/**
* @param {any} priorityRuleData

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue: any is not a valid JSDoc type

*/
function updatePriorityRule(priorityRuleData) {
priorityRule = priorityRuleData;
if (priorityRule) {
const link = document.createElement('a');
link.href = priorityRule.url;
link.textContent = priorityRule.priorityRule.name;
priorityRuleNameElem.replaceChildren(link);
} else {
priorityRuleNameElem.textContent = 'None';
}
vscode.setState({ score, priorityRule, footPrint, branch });
}

function updateFootPrint(footPrintData) {
footPrint = footPrintData;
footPrintElem.textContent = footPrint ? `${footPrint} kgCO2eq` : 'None';
vscode.setState({ score, priorityRule, footPrint, branch });
}

/**
Expand All @@ -53,7 +79,7 @@ import { addMessageHandler } from '../../services/message.service.mjs'
branches.forEach(branchData => {
const { name, isMain } = branchData;
const option = new Option(name, name);
Object.assign(option.dataset, branch);
Object.assign(option.dataset, branchData);
options.add(option);
if ((isMain && !newSelected) || (name === previousSelected)) {
newSelected = option;
Expand All @@ -63,7 +89,7 @@ import { addMessageHandler } from '../../services/message.service.mjs'
newSelected.selected = true;
}
branch = newSelected.dataset.name;
vscode.setState({ score, branch });
vscode.setState({ score, priorityRule, footPrint, branch });
vscode.postMessage({ type: 'selectBranch', message: branch });
}

Expand Down
15 changes: 14 additions & 1 deletion apps/vscode-sonar-extension/src/views/score/score.view.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,20 @@
</form>
</header>
<main>
<strong class="score">Score: Not Loaded</strong>
<ul>
<li>
<strong>Score:</strong>
<div class="score">Not Loaded</div>
</li>
<li>
<strong>Priority Rule:</strong>
<div class="priority-rule">Not Loaded</div>
</li>
<li>
<strong>Footprint:</strong>
<div class="footprint">Not Loaded</div>
</li>
</ul>
</main>
<footer>
<div>
Expand Down
61 changes: 59 additions & 2 deletions apps/vscode-sonar-extension/src/views/score/score.view.mjs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion(non-blocking): score.view.mjs files should be unit tested

Non blocking only because there no vscode unit tests yet
But highly recommended

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import vscode from '../../compat/vscode.cjs'

import { sync } from '../../services/configuration.service.mjs';
import { sync, getSonarConfiguration } from '../../services/configuration.service.mjs';
import { WebViewService } from '../../services/view.service.mjs';
import SonarAPI from '@creedengo/sonar-services'
import core from '@creedengo/core-services';

const { window, Uri } = vscode
const { getProjectBranches } = SonarAPI
const { api, calculateProjectScore } = core
const { api, calculateProjectScore, getPriorityRule, getFootprintEstimation } = core

api.init(SonarAPI)

Expand All @@ -33,6 +33,8 @@ export class CreedengoScoreViewProvider {
async resolveWebviewView(webviewView) {
const messageHandlers = {
updateBranches: () => this.updateScore(),
updatePriorityRule: () => this.updatePriorityRule(),
updateFootPrint: () => this.updateFootPrint(),
updateScore: () => this.updateScore(),
selectBranch: data => {
this.#currentBranch = data.message;
Expand All @@ -48,8 +50,63 @@ export class CreedengoScoreViewProvider {
})

await this.#updateBranches();
await this.updatePriorityRule();
await this.updateFootPrint();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

niptick: be consistent with the code base

The updateBranches() is private (defined by the JS standard symbol #), so should be the updatePriorityRule() and updateFootPrint() methods

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

typo: updateFootPrintshould be updateFootprint

Footprint is a single word

See: https://en.wikipedia.org/wiki/Carbon_footprint


webviewView.onDidChangeVisibility(async () => {
if (!webviewView.visible) {
return;
}
await this.#updateBranches();
await this.updateScore();
await this.updatePriorityRule();
await this.updateFootPrint();
})
}

async updatePriorityRule() {
if (!this.#service) {
return;
}
let priorityRule;
try {
const { ready, project } = await sync();
if (!ready || !project) {
return false
}
const branch = this.#currentBranch
priorityRule = await getPriorityRule({ project, branch });
} catch(error) {
window.showErrorMessage(error.message)
}
const { server } = await getSonarConfiguration();
this.#service.show(true);
const url = `${server}coding_rules?languages=${priorityRule.lang}&q=${priorityRule?.name.split(' ').join('+')}&open=java%3AS2187`
this.#service.postMessage({ type: 'updatePriorityRule', value: {
priorityRule: priorityRule,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

niptick: the syntax could be simplified

{ priorityRule, url } is enough

url: url,
} });
}

async updateFootPrint() {
if (!this.#service) {
return;
}
let footPrint;
try {
const { ready, project } = await sync();
if (!ready || !project) {
return false
}
const branch = this.#currentBranch
footPrint = await getFootprintEstimation({ project, branch });
} catch(error) {
window.showErrorMessage(error.message)
}
this.#service.show(true);
this.#service.postMessage({ type: 'updateFootPrint', value: footPrint });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue: the value is sent as a number without any formating

It should indicate the unit of measurement and the number of digits should be controlled

}

/**
* Request data from the Sonar Server to recalculate the sustainability score
* @returns
Expand Down
4 changes: 2 additions & 2 deletions shared/core-services/src/footprint.service.js

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

praise: thank you for this fix

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import api from './adapter.js'

async function getCastEstimation(config) {
const { project, branch } = config
const nGd = api.getIssueCount()
const mLoc = api.getNumberOfLineOfCode({ project, branch })
const nGd = api.services.getIssueCount(config)
const mLoc = api.services.getNumberOfLineOfCode({ project, branch })
const f = 0.004 / 100
const { nServer = 3 } = config
const { timeSharing = 1 } = config
Expand Down
12 changes: 7 additions & 5 deletions shared/core-services/src/footprint.service.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import { getFootprintEstimation } from './footprint.service'
import { describe } from 'vitest';


vi.mock('./adapter', async () => ({ default: {
getIssueCount: vi.fn(),
getNumberOfLineOfCode: vi.fn()
vi.mock('./adapter', async () => ({ default: {
services: {
getIssueCount: vi.fn(),
getNumberOfLineOfCode: vi.fn()
}
}}))

beforeAll(() => {
// Mock the API calls
api.getIssueCount.mockResolvedValue(5);
api.getNumberOfLineOfCode.mockResolvedValue(100);
api.services.getIssueCount.mockResolvedValue(5);
api.services.getNumberOfLineOfCode.mockResolvedValue(100);
});

afterAll(() => {
Expand Down
2 changes: 2 additions & 0 deletions shared/core-services/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import api from './adapter.js'
import settings from './settings.service.js'

import * as priorityRule from './priority-rule.service.js'
import * as footPrint from './footprint.service.js'
import * as score from './score.service.js'

export default {
api,
settings,
...priorityRule,
...footPrint,
...score
}
13 changes: 12 additions & 1 deletion shared/sonar-services/src/api/issues/sonar.issues.search.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ export async function findIssues({ project, branch }) {
return issues
}

export async function getIssueCount({ project, branch }) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue: this function should be unit tested

const searchParams = {
...sustainabilitySearchParams,
componentKeys: project,
branch,
ps: 1, // no issues parsing, we only want the total count
}
const page = await sonarRequestAPI.getJSON(routeUrl, searchParams)
return page.paging.total
}

function facetFormater(result, severity) {
const { val, count } = severity;
result[val.toLowerCase()] = count;
Expand All @@ -52,7 +63,7 @@ export async function getIssuesFacet(facetName, { project, branch, severity }) {
branch,
severity,
facets: facetName,
ps: 0, // no issues parsing, we only want the facets
ps: 1, // no issues parsing, we only want the facets
}
const { facets } = await sonarRequestAPI.getJSON(routeUrl, searchParams);

Expand Down
4 changes: 3 additions & 1 deletion shared/sonar-services/src/api/rules/sonar.rules.show.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ const routeUrl = `${API}/show`
* @returns Promise<Object>>
*/
export async function getRuleDetails(ruleKey) {
const searchParams = { 'rule_key': ruleKey }
const [prefix, rest] = ruleKey.split(':')
const keyParam = prefix.concat(':', rest.toUpperCase())
const searchParams = { 'key': keyParam }
const { rule } = await sonarRequestAPI.getJSON(routeUrl, searchParams)
return rule
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ afterEach(() => server.resetHandlers())

describe('getRuleDetails', () => {
test('getRuleDetails retrieve 1 rule', async () => {
const issues = await getRuleDetails({ project: 'foo', branch: 'master' })
const issues = await getRuleDetails('project: foo', 'branch: master')

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue: this function call is wrong, it expect only 1 param

The function signature is getRuleDetails(ruleKey) with a valid ruleKey

The force uppercasing part of the rule key, this behavior should be tested

expect(issues).toStrictEqual(mockRuleDetails.rule)
})
})
Loading