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
    String query =
52
        """
53
        query GetGroupSets($courseId: ID!) {
54
          course(id: $courseId) {
55
            groupSets {
56
              _id
57
              name
58
              id
59
            }
60
          }
61
        }
62
        """;
63
64
    HttpSyncGraphQlClient authedClient =
65
        graphQlClient
66
            .mutate()
67
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
68
            .build();
69
70
    List<CanvasGroupSet> groupSets =
71
        authedClient
72
            .document(query)
73
            .variable("courseId", course.getCanvasCourseId())
74
            .retrieveSync("course.groupSets")
75
            .toEntityList(CanvasGroupSet.class);
76 1 1. getCanvasGroupSets : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasGroupSets → KILLED
    return groupSets;
77
  }
78
79
  /**
80
   * Fetches the roster of students from Canvas for the given course.
81
   *
82
   * @param course the Course entity containing canvasApiToken and canvasCourseId
83
   * @return list of RosterStudent objects from Canvas
84
   */
85
  public List<RosterStudent> getCanvasRoster(@HasLinkedCanvasCourse Course course) {
86
    String query =
87
        """
88
              query GetRoster($courseId: ID!) {
89
              course(id: $courseId) {
90
                usersConnection(filter: {enrollmentTypes: StudentEnrollment}) {
91
                  edges {
92
                    node {
93
                      firstName
94
                      lastName
95
                      sisId
96
                      email
97
                      integrationId
98
                    }
99
                  }
100
                }
101
              }
102
            }
103
            """;
104
105
    HttpSyncGraphQlClient authedClient =
106
        graphQlClient
107
            .mutate()
108
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
109
            .build();
110
111
    List<CanvasStudent> students =
112
        authedClient
113
            .document(query)
114
            .variable("courseId", course.getCanvasCourseId())
115
            .retrieveSync("course.usersConnection.edges")
116
            .toEntityList(JsonNode.class)
117
            .stream()
118 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))
119
            .toList();
120
121 1 1. getCanvasRoster : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasRoster → KILLED
    return students.stream()
122
        .map(
123
            student ->
124 1 1. lambda$getCanvasRoster$1 : replaced return value with null for edu/ucsb/cs156/frontiers/services/CanvasService::lambda$getCanvasRoster$1 → KILLED
                RosterStudent.builder()
125
                    .firstName(student.getFirstName())
126
                    .lastName(student.getLastName())
127
                    .studentId(student.getStudentId())
128
                    .email(student.getEmail())
129
                    .build())
130
        .toList();
131
  }
132
133
  public List<CanvasGroup> getCanvasGroups(
134
      @HasLinkedCanvasCourse Course course, String groupSetId) {
135
    String query =
136
        """
137
            query GetTeams($groupId: ID!) {
138
              node(id: $groupId) {
139
                ... on GroupSet {
140
                  id
141
                  name
142
                  groups {
143
                    name
144
                    _id
145
                    membersConnection {
146
                      edges {
147
                        node {
148
                          user {
149
                            email
150
                          }
151
                        }
152
                      }
153
                    }
154
                  }
155
                }
156
              }
157
            }
158
            """;
159
160
    HttpSyncGraphQlClient authedClient =
161
        graphQlClient
162
            .mutate()
163
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
164
            .build();
165
166
    List<JsonNode> groups =
167
        authedClient
168
            .document(query)
169
            .variable("groupId", groupSetId)
170
            .retrieveSync("node.groups")
171
            .toEntityList(JsonNode.class);
172
173
    List<CanvasGroup> parsedGroups =
174
        groups.stream()
175
            .map(
176
                group -> {
177
                  CanvasGroup canvasGroup =
178
                      CanvasGroup.builder()
179
                          .name(group.get("name").asText())
180
                          .id(group.get("_id").asInt())
181
                          .members(new ArrayList<>())
182
                          .build();
183
                  group
184
                      .get("membersConnection")
185
                      .get("edges")
186 1 1. lambda$getCanvasGroups$3 : removed call to com/fasterxml/jackson/databind/JsonNode::forEach → KILLED
                      .forEach(
187
                          edge -> {
188
                            canvasGroup
189
                                .getMembers()
190
                                .add(
191
                                    CanonicalFormConverter.convertToValidEmail(
192
                                        edge.path("node").path("user").get("email").asText()));
193
                          });
194 1 1. lambda$getCanvasGroups$3 : replaced return value with null for edu/ucsb/cs156/frontiers/services/CanvasService::lambda$getCanvasGroups$3 → KILLED
                  return canvasGroup;
195
                })
196
            .toList();
197
198 1 1. getCanvasGroups : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasGroups → KILLED
    return parsedGroups;
199
  }
200
}

Mutations

76

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

118

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

121

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

124

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

186

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

194

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

198

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