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 <a
27
 * href="https://canvas.instructure.com/doc/api/">...</a>.
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: <a href="https://ucsb.instructure.com/graphiql">...</a>
33
 */
34
@Service
35
@Validated
36
public class CanvasService {
37
38
  private HttpSyncGraphQlClient graphQlClient;
39
  private ObjectMapper mapper;
40
41
  public CanvasService(ObjectMapper mapper, RestClient.Builder builder) {
42
    this.graphQlClient = HttpSyncGraphQlClient.builder(builder.build()).build();
43
    this.mapper = mapper;
44
    this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
45
  }
46
47
  public List<CanvasGroupSet> getCanvasGroupSets(@HasLinkedCanvasCourse Course course) {
48
    // language=GraphQL
49
    String query =
50
        """
51
        query GetGroupSets($courseId: ID!) {
52
          course(id: $courseId) {
53
            groupSets {
54
              _id
55
              name
56
              id
57
            }
58
          }
59
        }
60
        """;
61
62
    HttpSyncGraphQlClient authedClient =
63
        graphQlClient
64
            .mutate()
65
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
66
            .url(course.getSchool().getCanvasImplementation())
67
            .build();
68
69
    List<CanvasGroupSet> groupSets =
70
        authedClient
71
            .document(query)
72
            .variable("courseId", course.getCanvasCourseId())
73
            .retrieveSync("course.groupSets")
74
            .toEntityList(CanvasGroupSet.class);
75 1 1. getCanvasGroupSets : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasGroupSets → KILLED
    return groupSets;
76
  }
77
78
  /**
79
   * Fetches the roster of students from Canvas for the given course.
80
   *
81
   * @param course the Course entity containing canvasApiToken and canvasCourseId
82
   * @return list of RosterStudent objects from Canvas
83
   */
84
  public List<RosterStudent> getCanvasRoster(@HasLinkedCanvasCourse Course course) {
85
86
    // language=GraphQL
87
    String query =
88
        """
89
              query GetRoster($courseId: ID!) {
90
              course(id: $courseId) {
91
                usersConnection(filter: {enrollmentTypes: StudentEnrollment}) {
92
                  edges {
93
                    node {
94
                      firstName
95
                      lastName
96
                      sisId
97
                      email
98
                      integrationId
99
                    }
100
                  }
101
                }
102
              }
103
            }
104
            """;
105
106
    HttpSyncGraphQlClient authedClient =
107
        graphQlClient
108
            .mutate()
109
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
110
            .url(course.getSchool().getCanvasImplementation())
111
            .build();
112
113
    List<CanvasStudent> students =
114
        authedClient
115
            .document(query)
116
            .variable("courseId", course.getCanvasCourseId())
117
            .retrieveSync("course.usersConnection.edges")
118
            .toEntityList(JsonNode.class)
119
            .stream()
120 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))
121
            .toList();
122
123 1 1. getCanvasRoster : replaced return value with Collections.emptyList for edu/ucsb/cs156/frontiers/services/CanvasService::getCanvasRoster → KILLED
    return students.stream()
124
        .map(
125
            student ->
126 1 1. lambda$getCanvasRoster$1 : replaced return value with null for edu/ucsb/cs156/frontiers/services/CanvasService::lambda$getCanvasRoster$1 → KILLED
                RosterStudent.builder()
127
                    .firstName(student.getFirstName())
128
                    .lastName(student.getLastName())
129
                    .studentId(student.getStudentId())
130
                    .email(student.getEmail())
131
                    .build())
132
        .toList();
133
  }
134
135
  public List<CanvasGroup> getCanvasGroups(
136
      @HasLinkedCanvasCourse Course course, String groupSetId) {
137
    // language=GraphQL
138
    String query =
139
        """
140
            query GetTeams($groupId: ID!) {
141
              node(id: $groupId) {
142
                ... on GroupSet {
143
                  id
144
                  name
145
                  groups {
146
                    name
147
                    _id
148
                    membersConnection {
149
                      edges {
150
                        node {
151
                          user {
152
                            email
153
                          }
154
                        }
155
                      }
156
                    }
157
                  }
158
                }
159
              }
160
            }
161
            """;
162
163
    HttpSyncGraphQlClient authedClient =
164
        graphQlClient
165
            .mutate()
166
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
167
            .url(course.getSchool().getCanvasImplementation())
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

75

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

120

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

123

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

126

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