mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 15:18:32 -06:00
updates
This commit is contained in:
7
Management/DiagnosticsConfig.cs
Normal file
7
Management/DiagnosticsConfig.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
public static class DiagnosticsConfig
|
||||||
|
{
|
||||||
|
public const string SourceName = "canvas-management-source";
|
||||||
|
public static ActivitySource Source = new ActivitySource(SourceName);
|
||||||
|
}
|
||||||
@@ -36,6 +36,8 @@ public class QuizEditorContext(
|
|||||||
|
|
||||||
public void SaveQuiz(LocalQuiz newQuiz)
|
public void SaveQuiz(LocalQuiz newQuiz)
|
||||||
{
|
{
|
||||||
|
using var activity = DiagnosticsConfig.Source.CreateActivity("quiz creation requested", System.Diagnostics.ActivityKind.Server);
|
||||||
|
|
||||||
if (planner.LocalCourse != null && _module != null && Quiz != null)
|
if (planner.LocalCourse != null && _module != null && Quiz != null)
|
||||||
{
|
{
|
||||||
// use Quiz not newQuiz because it is the version that was last stored
|
// use Quiz not newQuiz because it is the version that was last stored
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ namespace Management.Services.Canvas;
|
|||||||
public class CanvasQuizService(
|
public class CanvasQuizService(
|
||||||
IWebRequestor webRequestor,
|
IWebRequestor webRequestor,
|
||||||
CanvasServiceUtils utils,
|
CanvasServiceUtils utils,
|
||||||
CanvasAssignmentService assignments
|
CanvasAssignmentService assignments,
|
||||||
|
ILogger<CanvasQuizService> logger
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
private readonly IWebRequestor webRequestor = webRequestor;
|
private readonly IWebRequestor webRequestor = webRequestor;
|
||||||
private readonly CanvasServiceUtils utils = utils;
|
private readonly CanvasServiceUtils utils = utils;
|
||||||
private readonly CanvasAssignmentService assignments = assignments;
|
private readonly CanvasAssignmentService assignments = assignments;
|
||||||
|
private readonly ILogger<CanvasQuizService> logger = logger;
|
||||||
|
|
||||||
public async Task<IEnumerable<CanvasQuiz>> GetAll(ulong courseId)
|
public async Task<IEnumerable<CanvasQuiz>> GetAll(ulong courseId)
|
||||||
{
|
{
|
||||||
@@ -33,6 +35,9 @@ public class CanvasQuizService(
|
|||||||
ulong? canvasAssignmentGroupId
|
ulong? canvasAssignmentGroupId
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
using var activity = DiagnosticsConfig.Source.StartActivity("Creating all canvas quiz");
|
||||||
|
activity?.SetCustomProperty("localQuiz", localQuiz);
|
||||||
|
activity?.SetTag("canvas syncronization", true);
|
||||||
Console.WriteLine($"Creating Quiz {localQuiz.Name}");
|
Console.WriteLine($"Creating Quiz {localQuiz.Name}");
|
||||||
|
|
||||||
var url = $"courses/{canvasCourseId}/quizzes";
|
var url = $"courses/{canvasCourseId}/quizzes";
|
||||||
@@ -59,6 +64,7 @@ public class CanvasQuizService(
|
|||||||
var (canvasQuiz, response) = await webRequestor.PostAsync<CanvasQuiz>(request);
|
var (canvasQuiz, response) = await webRequestor.PostAsync<CanvasQuiz>(request);
|
||||||
if (canvasQuiz == null)
|
if (canvasQuiz == null)
|
||||||
throw new Exception("Created canvas quiz was null");
|
throw new Exception("Created canvas quiz was null");
|
||||||
|
activity?.SetCustomProperty("canvasQuizId", canvasQuiz.Id);
|
||||||
|
|
||||||
await CreateQuizQuestions(canvasCourseId, canvasQuiz.Id, localQuiz);
|
await CreateQuizQuestions(canvasCourseId, canvasQuiz.Id, localQuiz);
|
||||||
return canvasQuiz.Id;
|
return canvasQuiz.Id;
|
||||||
@@ -70,15 +76,27 @@ public class CanvasQuizService(
|
|||||||
LocalQuiz localQuiz
|
LocalQuiz localQuiz
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var tasks = localQuiz.Questions.Select(createQuestion(canvasCourseId, canvasQuizId)).ToArray();
|
using var activity = DiagnosticsConfig.Source.StartActivity("Creating all quiz questions");
|
||||||
await Task.WhenAll(tasks);
|
activity?.SetCustomProperty("canvasQuizId", canvasQuizId);
|
||||||
|
activity?.SetTag("canvas syncronization", true);
|
||||||
|
|
||||||
|
|
||||||
|
var tasks = localQuiz.Questions.Select(
|
||||||
|
async (q, i) => await createQuestionOnly(canvasCourseId, canvasQuizId, q, i)
|
||||||
|
).ToArray();
|
||||||
|
var questionAndPositions = await Task.WhenAll(tasks);
|
||||||
|
await hackFixQuestionOrdering(canvasCourseId, canvasQuizId, questionAndPositions);
|
||||||
await hackFixRedundantAssignments(canvasCourseId);
|
await hackFixRedundantAssignments(canvasCourseId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task hackFixRedundantAssignments(ulong canvasCourseId)
|
private async Task hackFixRedundantAssignments(ulong canvasCourseId)
|
||||||
{
|
{
|
||||||
var canvasAssignments = await assignments.GetAll(canvasCourseId);
|
|
||||||
|
|
||||||
|
using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing redundant quiz assignments that are auto-created");
|
||||||
|
activity?.SetTag("canvas syncronization", true);
|
||||||
|
|
||||||
|
|
||||||
|
var canvasAssignments = await assignments.GetAll(canvasCourseId);
|
||||||
var assignmentsToDelete = canvasAssignments
|
var assignmentsToDelete = canvasAssignments
|
||||||
.Where(
|
.Where(
|
||||||
assignment =>
|
assignment =>
|
||||||
@@ -99,24 +117,44 @@ public class CanvasQuizService(
|
|||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Func<LocalQuizQuestion, Task> createQuestion(
|
private async Task hackFixQuestionOrdering(ulong canvasCourseId, ulong canvasQuizId, IEnumerable<(CanvasQuizQuestion question, int position)> questionAndPositions )
|
||||||
ulong canvasCourseId,
|
|
||||||
ulong canvasQuizId
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
return async (question) => await createQuestionOnly(canvasCourseId, canvasQuizId, question);
|
using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing question ordering with reorder");
|
||||||
|
activity?.SetCustomProperty("canvasQuizId", canvasQuizId);
|
||||||
|
activity?.SetTag("canvas syncronization", true);
|
||||||
|
|
||||||
|
var order = questionAndPositions.OrderBy(t => t.position).Select(tuple => {
|
||||||
|
return new {
|
||||||
|
type = "question",
|
||||||
|
id = tuple.question.Id.ToString(),
|
||||||
|
};
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
var url = $"courses/{canvasCourseId}/quizzes/{canvasQuizId}/reorder";
|
||||||
|
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
request.AddBody(new { order });
|
||||||
|
var response = await webRequestor.PostAsync(request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
throw new NullReferenceException("error re-ordering questions, reorder response is not successfull");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CanvasQuizQuestion> createQuestionOnly(
|
private async Task<(CanvasQuizQuestion question, int position)> createQuestionOnly(
|
||||||
ulong canvasCourseId,
|
ulong canvasCourseId,
|
||||||
ulong canvasQuizId,
|
ulong canvasQuizId,
|
||||||
LocalQuizQuestion q
|
LocalQuizQuestion q,
|
||||||
|
int position
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
using var activity = DiagnosticsConfig.Source.StartActivity("creating quiz question");
|
||||||
|
activity?.SetTag("canvas syncronization", true);
|
||||||
|
activity?.SetTag("localQuestion", q);
|
||||||
|
activity?.SetCustomProperty("localQuestion", q);
|
||||||
|
activity?.SetTag("success", false);
|
||||||
|
|
||||||
var url = $"courses/{canvasCourseId}/quizzes/{canvasQuizId}/questions";
|
var url = $"courses/{canvasCourseId}/quizzes/{canvasQuizId}/questions";
|
||||||
var answers = q.Answers
|
var answers = getAnswers(q);
|
||||||
.Select(a => new { answer_html = a.HtmlText, answer_weight = a.Correct ? 100 : 0 })
|
|
||||||
.ToArray();
|
|
||||||
var body = new
|
var body = new
|
||||||
{
|
{
|
||||||
question = new
|
question = new
|
||||||
@@ -124,16 +162,36 @@ public class CanvasQuizService(
|
|||||||
question_text = q.HtmlText,
|
question_text = q.HtmlText,
|
||||||
question_type = q.QuestionType + "_question",
|
question_type = q.QuestionType + "_question",
|
||||||
points_possible = q.Points,
|
points_possible = q.Points,
|
||||||
// position
|
position,
|
||||||
answers
|
answers
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var request = new RestRequest(url);
|
var request = new RestRequest(url);
|
||||||
request.AddBody(body);
|
request.AddBody(body);
|
||||||
|
|
||||||
var (newQuestion, response) = await webRequestor.PostAsync<CanvasQuizQuestion>(request);
|
var (newQuestion, response) = await webRequestor.PostAsync<CanvasQuizQuestion>(request);
|
||||||
if (newQuestion == null)
|
if (newQuestion == null)
|
||||||
throw new NullReferenceException("error creating new question, created question is null");
|
throw new NullReferenceException("error creating new question, created question is null");
|
||||||
|
|
||||||
return newQuestion;
|
|
||||||
|
activity?.SetCustomProperty("canvasQuizId", newQuestion.Id);
|
||||||
|
activity?.SetTag("success", true);
|
||||||
|
|
||||||
|
return (newQuestion, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object[] getAnswers(LocalQuizQuestion q)
|
||||||
|
{
|
||||||
|
if(q.QuestionType == QuestionType.MATCHING)
|
||||||
|
return q.Answers
|
||||||
|
.Select(a => new {
|
||||||
|
answer_match_left = a.Text,
|
||||||
|
answer_match_right = a.MatchedText
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return q.Answers
|
||||||
|
.Select(a => new { answer_html = a.HtmlText, answer_weight = a.Correct ? 100 : 0 })
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using LocalModels;
|
using LocalModels;
|
||||||
using Management.Services;
|
using Management.Services;
|
||||||
|
|
||||||
@@ -58,10 +57,3 @@ public class FileStorageManager
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class DiagnosticsConfig
|
|
||||||
{
|
|
||||||
public const string SourceName = "canvas-management-source";
|
|
||||||
public static ActivitySource Source = new ActivitySource(SourceName);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Net;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
|
|
||||||
@@ -7,10 +8,12 @@ public class WebRequestor : IWebRequestor
|
|||||||
private string token;
|
private string token;
|
||||||
private RestClient client;
|
private RestClient client;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
private readonly ILogger<WebRequestor> logger;
|
||||||
|
|
||||||
public WebRequestor(IConfiguration config)
|
public WebRequestor(IConfiguration config, ILogger<WebRequestor> logger)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
this.logger = logger;
|
||||||
token =
|
token =
|
||||||
_config["CANVAS_TOKEN"]
|
_config["CANVAS_TOKEN"]
|
||||||
?? throw new Exception("CANVAS_TOKEN not in environment");
|
?? throw new Exception("CANVAS_TOKEN not in environment");
|
||||||
@@ -33,22 +36,41 @@ public class WebRequestor : IWebRequestor
|
|||||||
|
|
||||||
public async Task<RestResponse> PostAsync(RestRequest request)
|
public async Task<RestResponse> PostAsync(RestRequest request)
|
||||||
{
|
{
|
||||||
|
using var activity = DiagnosticsConfig.Source.StartActivity("sending post");
|
||||||
|
activity?.AddTag("success", false);
|
||||||
|
|
||||||
request.AddHeader("Content-Type", "application/json");
|
request.AddHeader("Content-Type", "application/json");
|
||||||
var response = await client.ExecutePostAsync(request);
|
|
||||||
if (!response.IsSuccessful)
|
try
|
||||||
{
|
{
|
||||||
Console.WriteLine(response.Content);
|
var response = await client.ExecutePostAsync(request);
|
||||||
Console.WriteLine(response.ResponseUri);
|
activity?.AddTag("url", response.ResponseUri);
|
||||||
Console.WriteLine("error with response");
|
|
||||||
throw new Exception("error with response");
|
if (isRateLimited(response))
|
||||||
|
logger.LogInformation("hit rate limit");
|
||||||
|
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
logger.LogError($"Error with response, response content: {response.Content}", response);
|
||||||
|
throw new Exception("error with response");
|
||||||
|
}
|
||||||
|
activity?.AddTag("success", true);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("inside post catch block");
|
||||||
|
throw e;
|
||||||
|
|
||||||
}
|
}
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool isRateLimited(RestResponse response) =>
|
||||||
|
response.StatusCode == HttpStatusCode.Forbidden && response.Content?.Contains("403 Forbidden (Rate Limit Exceeded)") != null;
|
||||||
|
|
||||||
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
|
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
|
||||||
{
|
{
|
||||||
request.AddHeader("Content-Type", "application/json");
|
var response = await PostAsync(request);
|
||||||
var response = await client.ExecutePostAsync(request);
|
|
||||||
return (deserialize<T>(response), response);
|
return (deserialize<T>(response), response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,11 +97,38 @@ public class WebRequestor : IWebRequestor
|
|||||||
|
|
||||||
public async Task<RestResponse> DeleteAsync(RestRequest request)
|
public async Task<RestResponse> DeleteAsync(RestRequest request)
|
||||||
{
|
{
|
||||||
return await client.DeleteAsync(request);
|
// using var activity = DiagnosticsConfig.Source.StartActivity($"sending delete web request");
|
||||||
|
// activity?.AddTag("success", false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
var response = await client.DeleteAsync(request);
|
||||||
|
if (isRateLimited(response))
|
||||||
|
Console.WriteLine("after delete response in rate limited");
|
||||||
|
// Console.WriteLine(response.Content);
|
||||||
|
// activity?.AddTag("url", response.ResponseUri);
|
||||||
|
// activity?.AddTag("success", true);
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (HttpRequestException e)
|
||||||
|
{
|
||||||
|
if (e.StatusCode == HttpStatusCode.Forbidden) // && response.Content == "403 Forbidden (Rate Limit Exceeded)"
|
||||||
|
logger.LogInformation("hit rate limit in delete");
|
||||||
|
|
||||||
|
Console.WriteLine(e.StatusCode);
|
||||||
|
// Console.WriteLine();
|
||||||
|
throw e;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static T? deserialize<T>(RestResponse response)
|
private static T? deserialize<T>(RestResponse response)
|
||||||
{
|
{
|
||||||
|
// using var activity = DiagnosticsConfig.Source.StartActivity("deserializing response");
|
||||||
|
// activity?.AddTag("url", response.ResponseUri);
|
||||||
|
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
Console.WriteLine(response.Content);
|
Console.WriteLine(response.Content);
|
||||||
|
|||||||
46
canvas-development/docker-compose.yml
Normal file
46
canvas-development/docker-compose.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
services:
|
||||||
|
collector:
|
||||||
|
image: otel/opentelemetry-collector-contrib
|
||||||
|
volumes:
|
||||||
|
- ./otel-collector-config.yml:/etc/otelcol-contrib/config.yaml
|
||||||
|
ports:
|
||||||
|
- 1888:1888 # pprof extension
|
||||||
|
- 8888:8888 # Prometheus metrics exposed by the Collector
|
||||||
|
- 8889:8889 # Prometheus exporter metrics
|
||||||
|
- 13133:13133 # health_check extension
|
||||||
|
- 4317:4317 # OTLP gRPC receiver
|
||||||
|
- 4318:4318 # OTLP http receiver
|
||||||
|
- 55679:55679 # zpages extension
|
||||||
|
|
||||||
|
zipkin:
|
||||||
|
image: ghcr.io/openzipkin/zipkin-slim
|
||||||
|
# Environment settings are defined here https://github.com/openzipkin/zipkin/blob/master/zipkin-server/README.md#environment-variables
|
||||||
|
environment:
|
||||||
|
- STORAGE_TYPE=mem
|
||||||
|
ports:
|
||||||
|
- 9411:9411
|
||||||
|
# command: --logging.level.zipkin2=DEBUG
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana
|
||||||
|
user: 1000:1000
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- GF_AUTH_ANONYMOUS_ENABLED=true
|
||||||
|
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
|
||||||
|
# - GF_SECURITY_ADMIN_USER=admin
|
||||||
|
# - GF_SECURITY_ADMIN_PASSWORD=grafana
|
||||||
|
volumes:
|
||||||
|
- ./grafana-datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml
|
||||||
|
- canvas-management-grafana:/var/lib/grafana
|
||||||
|
- ./grafana-dashboard.yml:/etc/grafana/provisioning/dashboards/main.yaml
|
||||||
|
# - ./grafana-dashboard.json:/var/lib/grafana/dashboards/dash.json
|
||||||
|
|
||||||
|
loki:
|
||||||
|
image: grafana/loki:2.9.0
|
||||||
|
command: -config.file=/etc/loki/local-config.yaml
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
canvas-management-grafana:
|
||||||
295
canvas-development/grafana-dashboard.json
Normal file
295
canvas-development/grafana-dashboard.json
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": {
|
||||||
|
"type": "grafana",
|
||||||
|
"uid": "-- Grafana --"
|
||||||
|
},
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 0,
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "P8E80F9AEF21F6940"
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 3,
|
||||||
|
"options": {
|
||||||
|
"dedupStrategy": "none",
|
||||||
|
"enableLogDetails": true,
|
||||||
|
"prettifyLogMessage": false,
|
||||||
|
"showCommonLabels": false,
|
||||||
|
"showLabels": false,
|
||||||
|
"showTime": false,
|
||||||
|
"sortOrder": "Descending",
|
||||||
|
"wrapLogMessage": false
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "loki",
|
||||||
|
"uid": "P8E80F9AEF21F6940"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "{exporter=\"OTLP\"} | json | line_format \"{{.job}} - {{.date}}{{.body}}\"\n",
|
||||||
|
"key": "Q-4bb1fa82-7097-421e-80c0-e77e5267bd1d-0",
|
||||||
|
"queryType": "range",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "New Panel",
|
||||||
|
"transformations": [],
|
||||||
|
"type": "logs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "PBFA97CFB590B2093"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"__systemRef": "hideSeriesFrom",
|
||||||
|
"matcher": {
|
||||||
|
"id": "byNames",
|
||||||
|
"options": {
|
||||||
|
"mode": "exclude",
|
||||||
|
"names": [
|
||||||
|
"{__name__=\"http_server_active_requests\", http_request_method=\"GET\", instance=\"api:3000\", job=\"ApiMetrics\", url_scheme=\"http\"}"
|
||||||
|
],
|
||||||
|
"prefix": "All except:",
|
||||||
|
"readOnly": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "custom.hideFrom",
|
||||||
|
"value": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 8,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 8
|
||||||
|
},
|
||||||
|
"id": 2,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "PBFA97CFB590B2093"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "http_server_active_requests",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "__auto",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Panel Title",
|
||||||
|
"type": "timeseries"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "PBFA97CFB590B2093"
|
||||||
|
},
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisBorderShow": false,
|
||||||
|
"axisCenteredZero": false,
|
||||||
|
"axisColorMode": "text",
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"barAlignment": 0,
|
||||||
|
"drawStyle": "line",
|
||||||
|
"fillOpacity": 0,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false,
|
||||||
|
"viz": false
|
||||||
|
},
|
||||||
|
"insertNulls": false,
|
||||||
|
"lineInterpolation": "linear",
|
||||||
|
"lineWidth": 1,
|
||||||
|
"pointSize": 5,
|
||||||
|
"scaleDistribution": {
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"showPoints": "auto",
|
||||||
|
"spanNulls": false,
|
||||||
|
"stacking": {
|
||||||
|
"group": "A",
|
||||||
|
"mode": "none"
|
||||||
|
},
|
||||||
|
"thresholdsStyle": {
|
||||||
|
"mode": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 7,
|
||||||
|
"w": 24,
|
||||||
|
"x": 0,
|
||||||
|
"y": 16
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
"options": {
|
||||||
|
"legend": {
|
||||||
|
"calcs": [],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "bottom",
|
||||||
|
"showLegend": true
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single",
|
||||||
|
"sort": "none"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": {
|
||||||
|
"type": "prometheus",
|
||||||
|
"uid": "PBFA97CFB590B2093"
|
||||||
|
},
|
||||||
|
"editorMode": "code",
|
||||||
|
"expr": "rate(hit_get_total [1m])",
|
||||||
|
"instant": false,
|
||||||
|
"legendFormat": "__auto",
|
||||||
|
"range": true,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Panel Title",
|
||||||
|
"type": "timeseries"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "5s",
|
||||||
|
"schemaVersion": 39,
|
||||||
|
"tags": [],
|
||||||
|
"templating": {
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"time": {
|
||||||
|
"from": "now-15m",
|
||||||
|
"to": "now"
|
||||||
|
},
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "get request rate",
|
||||||
|
"uid": "c9342e0c-c83c-4bf3-a801-b23a89deac88",
|
||||||
|
"version": 4,
|
||||||
|
"weekStart": ""
|
||||||
|
}
|
||||||
12
canvas-development/grafana-dashboard.yml
Normal file
12
canvas-development/grafana-dashboard.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
providers:
|
||||||
|
- name: "Dashboard provider"
|
||||||
|
orgId: 1
|
||||||
|
type: file
|
||||||
|
disableDeletion: false
|
||||||
|
updateIntervalSeconds: 10
|
||||||
|
allowUiUpdates: false
|
||||||
|
options:
|
||||||
|
path: /var/lib/grafana/dashboards
|
||||||
|
foldersFromFilesStructure: true
|
||||||
20
canvas-development/grafana-datasource.yml
Normal file
20
canvas-development/grafana-datasource.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: Prometheus
|
||||||
|
type: prometheus
|
||||||
|
url: http://prometheus:9090
|
||||||
|
isDefault: true
|
||||||
|
access: proxy
|
||||||
|
editable: true
|
||||||
|
- name: Loki
|
||||||
|
type: loki
|
||||||
|
access: proxy
|
||||||
|
orgId: 1
|
||||||
|
url: http://loki:3100
|
||||||
|
basicAuth: false
|
||||||
|
version: 1
|
||||||
|
editable: false
|
||||||
|
- name: Zipkin
|
||||||
|
type: zipkin
|
||||||
|
url: http://zipkin:9411
|
||||||
@@ -10,8 +10,12 @@ processors:
|
|||||||
exporters:
|
exporters:
|
||||||
# otlp:
|
# otlp:
|
||||||
# endpoint: otelcol:4317
|
# endpoint: otelcol:4317
|
||||||
|
# prometheus:
|
||||||
|
# endpoint: "0.0.0.0:1234"
|
||||||
zipkin:
|
zipkin:
|
||||||
endpoint: http://zipkin:9411/api/v2/spans
|
endpoint: http://zipkin:9411/api/v2/spans
|
||||||
|
loki:
|
||||||
|
endpoint: "http://loki:3100/loki/api/v1/push"
|
||||||
|
|
||||||
extensions:
|
extensions:
|
||||||
health_check:
|
health_check:
|
||||||
@@ -26,10 +30,10 @@ service:
|
|||||||
processors: [batch]
|
processors: [batch]
|
||||||
exporters: [zipkin]
|
exporters: [zipkin]
|
||||||
# metrics:
|
# metrics:
|
||||||
# receivers: []
|
# receivers: [otlp]
|
||||||
# processors: [batch]
|
# processors: [batch]
|
||||||
# exporters: []
|
# exporters: [prometheus]
|
||||||
# logs:
|
logs:
|
||||||
# receivers: []
|
receivers: [otlp]
|
||||||
# processors: [batch]
|
processors: [batch]
|
||||||
# exporters: []
|
exporters: [loki]
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
services:
|
|
||||||
collector:
|
|
||||||
image: otel/opentelemetry-collector-contrib
|
|
||||||
volumes:
|
|
||||||
- ../otel-collector-config.yml:/etc/otelcol-contrib/config.yaml
|
|
||||||
ports:
|
|
||||||
- 1888:1888 # pprof extension
|
|
||||||
- 8888:8888 # Prometheus metrics exposed by the Collector
|
|
||||||
- 8889:8889 # Prometheus exporter metrics
|
|
||||||
- 13133:13133 # health_check extension
|
|
||||||
- 4317:4317 # OTLP gRPC receiver
|
|
||||||
- 4318:4318 # OTLP http receiver
|
|
||||||
- 55679:55679 # zpages extension
|
|
||||||
|
|
||||||
zipkin:
|
|
||||||
image: ghcr.io/openzipkin/zipkin-slim
|
|
||||||
container_name: zipkin
|
|
||||||
# Environment settings are defined here https://github.com/openzipkin/zipkin/blob/master/zipkin-server/README.md#environment-variables
|
|
||||||
environment:
|
|
||||||
- STORAGE_TYPE=mem
|
|
||||||
ports:
|
|
||||||
- 9411:9411
|
|
||||||
# command: --logging.level.zipkin2=DEBUG
|
|
||||||
@@ -2,6 +2,30 @@
|
|||||||
GET https://snow.instructure.com/api/v1/courses/958185/quizzes
|
GET https://snow.instructure.com/api/v1/courses/958185/quizzes
|
||||||
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||||
|
|
||||||
|
###
|
||||||
|
GET https://snow.instructure.com/api/v1/courses/926068/quizzes/3489332/questions
|
||||||
|
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
POST https://snow.instructure.com/api/v1/courses/926068/quizzes/3489331/reorder
|
||||||
|
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"order":[
|
||||||
|
{"type":"question","id":"60973974"},
|
||||||
|
{"type":"question","id":"60973977"},
|
||||||
|
{"type":"question","id":"60973976"},
|
||||||
|
{"type":"question","id":"60973975"},
|
||||||
|
{"type":"question","id":"60973978"},
|
||||||
|
{"type":"question","id":"60973979"},
|
||||||
|
{"type":"question","id":"60973980"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
GET https://snow.instructure.com/api/v1/courses/958185/assignments
|
GET https://snow.instructure.com/api/v1/courses/958185/assignments
|
||||||
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||||
@@ -77,4 +101,4 @@ Content-Type: application/json
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user