CanvasService.java

1
package edu.ucsb.cs156.frontiers.services;
2
3
import com.fasterxml.jackson.databind.DeserializationFeature;
4
import com.fasterxml.jackson.databind.JsonNode;
5
import com.fasterxml.jackson.databind.ObjectMapper;
6
import edu.ucsb.cs156.frontiers.entities.Course;
7
import edu.ucsb.cs156.frontiers.entities.RosterStudent;
8
import edu.ucsb.cs156.frontiers.models.CanvasGroup;
9
import edu.ucsb.cs156.frontiers.models.CanvasGroupSet;
10
import edu.ucsb.cs156.frontiers.models.CanvasStudent;
11
import edu.ucsb.cs156.frontiers.utilities.CanonicalFormConverter;
12
import edu.ucsb.cs156.frontiers.validators.HasLinkedCanvasCourse;
13
import java.util.ArrayList;
14
import java.util.List;
15
import org.springframework.graphql.client.HttpSyncGraphQlClient;
16
import org.springframework.stereotype.Service;
17
import org.springframework.validation.annotation.Validated;
18
import org.springframework.web.client.RestClient;
19
20
/**
21
 * Service for interacting with the Canvas API.
22
 *
23
 * <p>Note that the Canvas API uses a GraphQL endpoint, which allows for more flexible queries
24
 * compared to traditional REST APIs.
25
 *
26
 * <p>For more information on the Canvas API, visit the official documentation at
27
 * https://canvas.instructure.com/doc/api/.
28
 *
29
 * <p>You can typically interact with Canvas API GraphQL endpoints interactively by appending
30
 * /graphiql to the URL of the Canvas instance.
31
 *
32
 * <p>For example, for UCSB Canvas, use: https://ucsb.instructure.com/graphiql
33
 */
34
@Service
35
@Validated
36
public class CanvasService {
37
38
  private HttpSyncGraphQlClient graphQlClient;
39
  private ObjectMapper mapper;
40
41
  private static final String CANVAS_GRAPHQL_URL = "https://ucsb.instructure.com/api/graphql";
42
43
  public CanvasService(ObjectMapper mapper, RestClient.Builder builder) {
44
    this.graphQlClient =
45
        HttpSyncGraphQlClient.builder(builder.baseUrl(CANVAS_GRAPHQL_URL).build()).build();
46
    this.mapper = mapper;
47
    this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
48
  }
49
50
  public List<CanvasGroupSet> getCanvasGroupSets(@HasLinkedCanvasCourse Course course) {
51
    // language=GraphQL
52
    String query =
53
        """
54
        query GetGroupSets($courseId: ID!) {
55
          course(id: $courseId) {
56
            groupSets {
57
              _id
58
              name
59
              id
60
            }
61
          }
62
        }
63
        """;
64
65
    HttpSyncGraphQlClient authedClient =
66
        graphQlClient
67
            .mutate()
68
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
69
            .build();
70
71
    List<CanvasGroupSet> groupSets =
72
        authedClient
73
            .document(query)
74
            .variable("courseId", course.getCanvasCourseId())
75
            .retrieveSync("course.groupSets")
76
            .toEntityList(CanvasGroupSet.class);
77 1 1. getCanvasGroupSets : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasGroupSets → KILLED
    return groupSets;
78
  }
79
80
  /**
81
   * Fetches the roster of students from Canvas for the given course.
82
   *
83
   * @param course the Course entity containing canvasApiToken and canvasCourseId
84
   * @return list of RosterStudent objects from Canvas
85
   */
86
  public List<RosterStudent> getCanvasRoster(@HasLinkedCanvasCourse Course course) {
87
88
    // language=GraphQL
89
    String query =
90
        """
91
              query GetRoster($courseId: ID!) {
92
              course(id: $courseId) {
93
                usersConnection(filter: {enrollmentTypes: StudentEnrollment}) {
94
                  edges {
95
                    node {
96
                      firstName
97
                      lastName
98
                      sisId
99
                      email
100
                      integrationId
101
                    }
102
                  }
103
                }
104
              }
105
            }
106
            """;
107
108
    HttpSyncGraphQlClient authedClient =
109
        graphQlClient
110
            .mutate()
111
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
112
            .build();
113
114
    List<CanvasStudent> students =
115
        authedClient
116
            .document(query)
117
            .variable("courseId", course.getCanvasCourseId())
118
            .retrieveSync("course.usersConnection.edges")
119
            .toEntityList(JsonNode.class)
120
            .stream()
121 1 1. lambda$getCanvasRoster$0 : replaced return value with null for edu/ucsb/cs156/frontiers/services/CanvasService::lambda$getCanvasRoster$0 → KILLED
            .map(node -> mapper.convertValue(node.get("node"), CanvasStudent.class))
122
            .toList();
123
124 1 1. getCanvasRoster : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasRoster → KILLED
    return students.stream()
125
        .map(
126
            student ->
127 1 1. lambda$getCanvasRoster$1 : replaced return value with null for edu/ucsb/cs156/frontiers/services/CanvasService::lambda$getCanvasRoster$1 → KILLED
                RosterStudent.builder()
128
                    .firstName(student.getFirstName())
129
                    .lastName(student.getLastName())
130
                    .studentId(student.getStudentId())
131
                    .email(student.getEmail())
132
                    .build())
133
        .toList();
134
  }
135
136
  public List<CanvasGroup> getCanvasGroups(
137
      @HasLinkedCanvasCourse Course course, String groupSetId) {
138
    // language=GraphQL
139
    String query =
140
        """
141
            query GetTeams($groupId: ID!) {
142
              node(id: $groupId) {
143
                ... on GroupSet {
144
                  id
145
                  name
146
                  groups {
147
                    name
148
                    _id
149
                    membersConnection {
150
                      edges {
151
                        node {
152
                          user {
153
                            email
154
                          }
155
                        }
156
                      }
157
                    }
158
                  }
159
                }
160
              }
161
            }
162
            """;
163
164
    HttpSyncGraphQlClient authedClient =
165
        graphQlClient
166
            .mutate()
167
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
168
            .build();
169
170
    List<JsonNode> groups =
171
        authedClient
172
            .document(query)
173
            .variable("groupId", groupSetId)
174
            .retrieveSync("node.groups")
175
            .toEntityList(JsonNode.class);
176
177
    List<CanvasGroup> parsedGroups =
178
        groups.stream()
179
            .map(
180
                group -> {
181
                  CanvasGroup canvasGroup =
182
                      CanvasGroup.builder()
183
                          .name(group.get("name").asText())
184
                          .id(group.get("_id").asInt())
185
                          .members(new ArrayList<>())
186
                          .build();
187
                  group
188
                      .get("membersConnection")
189
                      .get("edges")
190 1 1. lambda$getCanvasGroups$3 : removed call to com/fasterxml/jackson/databind/JsonNode::forEach → KILLED
                      .forEach(
191
                          edge -> {
192
                            canvasGroup
193
                                .getMembers()
194
                                .add(
195
                                    CanonicalFormConverter.convertToValidEmail(
196
                                        edge.path("node").path("user").get("email").asText()));
197
                          });
198 1 1. lambda$getCanvasGroups$3 : replaced return value with null for edu/ucsb/cs156/frontiers/services/CanvasService::lambda$getCanvasGroups$3 → KILLED
                  return canvasGroup;
199
                })
200
            .toList();
201
202 1 1. getCanvasGroups : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasGroups → KILLED
    return parsedGroups;
203
  }
204
}

Mutations

77

1.1
Location : getCanvasGroupSets
Killed by : edu.ucsb.cs156.frontiers.services.CanvasServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.CanvasServiceTests]/[method:testGetCanvasGroupSets_returnsGroupSets()]
replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasGroupSets → KILLED

121

1.1
Location : lambda$getCanvasRoster$0
Killed by : edu.ucsb.cs156.frontiers.services.CanvasServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.CanvasServiceTests]/[method:testGetCanvasRoster_usesIntegrationIdWhenPresent()]
replaced return value with null for edu/ucsb/cs156/frontiers/services/CanvasService::lambda$getCanvasRoster$0 → KILLED

124

1.1
Location : getCanvasRoster
Killed by : edu.ucsb.cs156.frontiers.services.CanvasServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.CanvasServiceTests]/[method:testGetCanvasRoster_usesIntegrationIdWhenPresent()]
replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasRoster → KILLED

127

1.1
Location : lambda$getCanvasRoster$1
Killed by : edu.ucsb.cs156.frontiers.services.CanvasServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.CanvasServiceTests]/[method:testGetCanvasRoster_usesIntegrationIdWhenPresent()]
replaced return value with null for edu/ucsb/cs156/frontiers/services/CanvasService::lambda$getCanvasRoster$1 → KILLED

190

1.1
Location : lambda$getCanvasGroups$3
Killed by : edu.ucsb.cs156.frontiers.services.CanvasServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.CanvasServiceTests]/[method:testGetCanvasGroups_returnsGroups()]
removed call to com/fasterxml/jackson/databind/JsonNode::forEach → KILLED

198

1.1
Location : lambda$getCanvasGroups$3
Killed by : edu.ucsb.cs156.frontiers.services.CanvasServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.CanvasServiceTests]/[method:testGetCanvasGroups_handlesGroupWithNoMembers()]
replaced return value with null for edu/ucsb/cs156/frontiers/services/CanvasService::lambda$getCanvasGroups$3 → KILLED

202

1.1
Location : getCanvasGroups
Killed by : edu.ucsb.cs156.frontiers.services.CanvasServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.services.CanvasServiceTests]/[method:testGetCanvasGroups_handlesGroupWithNoMembers()]
replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasGroups → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0