Skip to content

Commit

Permalink
Merge pull request #366 from fujaba/feat/codesearch-config
Browse files Browse the repository at this point in the history
Toggle Code Search, Snippet Suggestions and Similar Solutions in Config
  • Loading branch information
Clashsoft authored Oct 23, 2023
2 parents a9baeb4 + 23be303 commit fad127b
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,35 +61,37 @@
Code Search will only look for files matching the glob pattern <code>{{ task.glob }}</code>.
</span>
</div>
<label class="form-label" for="embedding-snippets">
Snippet Suggestions
<span class="badge bg-gradient-primary">New</span>
&nbsp;
<i class="bi-question-circle" [ngbTooltip]='"Code Search suggests code that is related to the task \"" + task?.description + "\"" '></i>
</label>
<ul class="list-group" id="embedding-snippets">
<li *ngFor="let snippet of embeddingSnippets; let first=first" class="list-group-item">
<app-snippet [snippet]="snippet" [expanded]="first" (confirmed)="confirmEmbedding(snippet)"></app-snippet>
</li>
</ul>
<ng-container *ngIf="snippetSuggestionsEnabled && embeddingSnippets.length">
<label class="form-label" for="embedding-snippets">
Snippet Suggestions
<span class="badge bg-gradient-primary">New</span>
&nbsp;
<i class="bi-question-circle" [ngbTooltip]='"Code Search suggests code that is related to the task \"" + task?.description + "\"" '></i>
</label>
<ul class="list-group" id="embedding-snippets">
<li *ngFor="let snippet of embeddingSnippets; let first=first" class="list-group-item">
<app-snippet [snippet]="snippet" [expanded]="first" (confirmed)="confirmEmbedding(snippet)"></app-snippet>
</li>
</ul>
</ng-container>
</ng-container>
<ng-container modal-footer>
<div class="form-check">
<div class="form-check" *ngIf="codeSearchEnabled">
<input type="checkbox" class="form-check-input" id="codeSearchCheck" [(ngModel)]="dto.codeSearch">
<label for="codeSearchCheck" class="form-check-label"
ngbTooltip="Use Code Search to create or update derived evaluations in other solutions">
Code Search
</label>
</div>
<div class="form-check" *ngIf="!evaluation">
<div class="form-check" *ngIf="similarSolutionsEnabled && !evaluation">
<input type="checkbox" class="form-check-input" id="similarCheck" [(ngModel)]="viewSimilar">
<label for="similarCheck" class="form-check-label"
ngbTooltip="View similar solutions to quickly copy this evaluation">
View Similar Solutions
<span class="badge bg-gradient-primary">New</span>
</label>
</div>
<a routerLink="similar" *ngIf="evaluation">
<a routerLink="similar" *ngIf="similarSolutionsEnabled && evaluation">
Similar Solutions
</a>
<button type="button" class="btn btn-secondary" (click)="modal.close()">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export class EvaluationModalComponent implements OnInit, OnDestroy {

readonly selectionComment = selectionComment;

codeSearchEnabled = this.configService.getBool('codeSearch');
snippetSuggestionsEnabled = this.configService.getBool('snippetSuggestions');
similarSolutionsEnabled = this.configService.getBool('similarSolutions');

task?: Task;
comments: string[] = [];
evaluation?: Evaluation;
Expand Down Expand Up @@ -71,7 +75,7 @@ export class EvaluationModalComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.route.params.pipe(
switchMap(({aid, task}) => this.assignmentService.get(aid).pipe(
tap(assignment => this.dto.codeSearch = !!assignment.classroom?.codeSearch),
tap(assignment => this.dto.codeSearch = this.codeSearchEnabled && !!assignment.classroom?.codeSearch),
map(assignment => this.taskService.find(assignment.tasks, task)),
)),
).subscribe(task => {
Expand Down Expand Up @@ -112,9 +116,11 @@ export class EvaluationModalComponent implements OnInit, OnDestroy {
switchMap(({aid, task}) => this.evaluationService.distinctValues<string>(aid, 'snippets.comment', {task})),
).subscribe(comments => this.comments = comments);

this.route.params.pipe(
switchMap(({aid, sid, task}) => this.embeddingService.findTaskRelatedSnippets(aid, sid, task)),
).subscribe(snippets => this.embeddingSnippets = snippets);
if (this.snippetSuggestionsEnabled) {
this.route.params.pipe(
switchMap(({aid, sid, task}) => this.embeddingService.findTaskRelatedSnippets(aid, sid, task)),
).subscribe(snippets => this.embeddingSnippets = snippets);
}

const selection$ = this.route.params.pipe(
switchMap(({aid, sid}) => this.selectionService.stream(aid, sid)),
Expand All @@ -134,37 +140,39 @@ export class EvaluationModalComponent implements OnInit, OnDestroy {
setTimeout(() => document.getElementById('snippet-' + index)?.focus());
}));

this.subscriptions.add(merge(
selection$.pipe(map(sel => sel.snippet.code)),
this.snippetUpdates$.pipe(map(snippet => snippet.pattern || snippet.code)),
).pipe(
debounceTime(200),
distinctUntilChanged(),
switchMap(code => this.assignmentService.searchSummary(this.route.snapshot.params.aid, code, this.task?.glob, '***').pipe(
map(searchSummary => ({...searchSummary, code})),
)),
).subscribe(summary => {
let level: string;
let message: string | undefined;
if (!summary.hits) {
level = 'warning';
message = 'No result indicates the snippet is not part of the submitted code for this solution. Please make sure you checked out the correct commit.';
} else if (summary.files > summary.solutions) {
level = 'danger';
message = 'The snippet was found in multiple files per solution. It most likely does not provide enough context.';
} else if (summary.hits > summary.files) {
level = 'warning';
message = 'The snippet was found in multiple places per file. It probably does not provide enough context.';
} else {
level = 'success';
}

this.searchSummary = {
...summary,
level,
message,
};
}));
if (this.codeSearchEnabled) {
this.subscriptions.add(merge(
selection$.pipe(map(sel => sel.snippet.code)),
this.snippetUpdates$.pipe(map(snippet => snippet.pattern || snippet.code)),
).pipe(
debounceTime(200),
distinctUntilChanged(),
switchMap(code => this.assignmentService.searchSummary(this.route.snapshot.params.aid, code, this.task?.glob, '***').pipe(
map(searchSummary => ({...searchSummary, code})),
)),
).subscribe(summary => {
let level: string;
let message: string | undefined;
if (!summary.hits) {
level = 'warning';
message = 'No result indicates the snippet is not part of the submitted code for this solution. Please make sure you checked out the correct commit.';
} else if (summary.files > summary.solutions) {
level = 'danger';
message = 'The snippet was found in multiple files per solution. It most likely does not provide enough context.';
} else if (summary.hits > summary.files) {
level = 'warning';
message = 'The snippet was found in multiple places per file. It probably does not provide enough context.';
} else {
level = 'success';
}

this.searchSummary = {
...summary,
level,
message,
};
}));
}
}

ngOnDestroy(): void {
Expand Down
34 changes: 33 additions & 1 deletion frontend/src/app/assignment/services/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export type ConfigKey =
| 'ide'
| 'cloneProtocol'
| 'cloneRef'
| 'codeSearch'
| 'similarSolutions'
| 'snippetSuggestions'
;

export interface ConfigOption {
Expand Down Expand Up @@ -49,7 +52,28 @@ export const CONFIG_OPTIONS: ConfigOption[] = [
'Tags are only supported in VSCode v1.74+ and Assignments imported after 2022-12-21.',
options: [['none', 'None'], ['tag', 'Tag']],
default: 'tag',
}
},
{
key: 'codeSearch',
title: 'Code Search',
description: 'Enable Code Search globally.',
options: [['true', '✔️ Enabled'], ['false', '❌ Disabled']],
default: 'true',
},
{
key: 'snippetSuggestions',
title: 'Snippet Suggestions',
description: 'Enable Snippet Suggestions globally.',
options: [['true', '✔️ Enabled'], ['false', '❌ Disabled']],
default: 'true',
},
{
key: 'similarSolutions',
title: 'Similar Solutions',
description: 'Enable Similar Solutions globally.',
options: [['true', '✔️ Enabled'], ['false', '❌ Disabled']],
default: 'true',
},
];

@Injectable()
Expand Down Expand Up @@ -78,12 +102,20 @@ export class ConfigService {
return this.privacyService.getStorage('assignments/' + key) || option?.default || '';
}

getBool(key: ConfigKey): boolean {
return this.get(key) === 'true';
}

set(key: ConfigKey, value: string) {
if (this.getOption(key)) {
this.privacyService.setStorage('assignments/' + key, value);
}
}

setBool(key: ConfigKey, value: boolean) {
this.set(key, value ? 'true' : 'false');
}

private getOption(key: ConfigKey): ConfigOption | undefined {
return CONFIG_OPTIONS.find(o => o.key === key);
}
Expand Down

0 comments on commit fad127b

Please sign in to comment.