• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MarkUsProject / Markus / 17596524523

09 Sep 2025 09:44PM UTC coverage: 91.815% (-0.03%) from 91.849%
17596524523

Pull #7663

github

web-flow
Merge 4c5631334 into bd307134c
Pull Request #7663: Convert Create Group modal to React component

683 of 1464 branches covered (46.65%)

Branch coverage included in aggregate %.

42136 of 45172 relevant lines covered (93.28%)

118.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/app/javascript/Components/Result/url_viewer.jsx
1
import React from "react";
2

3
export class URLViewer extends React.Component {
4
  constructor(props) {
5
    super(props);
×
6
    this.state = {
×
7
      externalURL: "",
8
      embeddedURL: "",
9
    };
10
  }
11

12
  componentDidMount() {
13
    this.fetchUrl();
×
14
  }
15

16
  componentDidUpdate(prevProps, prevState) {
17
    if (prevProps.url !== this.props.url) {
×
18
      this.fetchUrl();
×
19
    } else if (prevState.externalURL !== this.state.externalURL) {
×
20
      this.configDisplay();
×
21
    }
22
  }
23

24
  fetchUrl = () => {
×
25
    if (!this.props.url) {
×
26
      return;
×
27
    }
28
    this.props.setLoadingCallback(true);
×
29
    fetch(this.props.url)
×
30
      .then(response => response.text())
×
31
      .then(content =>
32
        this.setState({externalURL: content}, () => this.props.setLoadingCallback(false))
×
33
      )
34
      .catch(error => {
35
        this.props.setLoadingCallback(false);
×
36
        if (error instanceof DOMException) return;
×
37
        console.error(error);
×
38
      });
39
  };
40

41
  configDisplay = () => {
×
42
    const url = this.isValidURL(this.state.externalURL);
×
43
    if (!url) {
×
44
      return;
×
45
    }
46
    switch (url.hostname) {
×
47
      case "docs.google.com":
48
      case "drive.google.com":
49
        this.configureGoogleDrivePreview(url);
×
50
        break;
×
51
      case "www.youtube.com":
52
      case "youtu.be":
53
        this.configureOEmbedPreview(url.toString(), "https://www.youtube.com/oembed");
×
54
        break;
×
55
      default:
56
        this.setState({embeddedURL: ""});
×
57
    }
58
  };
59

60
  /*
61
   * Takes the content from a markusurl and checks to see if it is valid HTTP(S) url.
62
   * Returns a url object if it is valid and false otherwise.
63
   */
64
  isValidURL = urlStr => {
×
65
    try {
×
66
      const url = new URL(urlStr);
×
67
      if ((url.protocol === "http:" || url.protocol === "https:") && url.hostname !== "") {
×
68
        return url;
×
69
      }
70
    } catch (e) {
71
      //Invalid URL
72
    }
73
    return false;
×
74
  };
75

76
  /*
77
   * Converts a URL object google drive/docs link into a link that allows for embedding of the associated content.
78
   * This link is assumed to be of the form:
79
   *     (docs or drive).google.com/<content_type>/.../<view_mode>
80
   * This then sets the embeddedURL state to that newly converted link
81
   */
82
  configureGoogleDrivePreview = url => {
×
83
    const path = url.pathname.split("/");
×
84
    if (path[1] === "forms") {
×
85
      path[path.length - 1] = "/viewform";
×
86
    } else {
87
      path[path.length - 1] = "/preview";
×
88
    }
89
    url.pathname = path.join("/");
×
90
    this.setState({
×
91
      embeddedURL: url.toString(),
92
    });
93
  };
94

95
  /*
96
   * Asynchronous function that fetches the embedded representation of a video URL using the oembed format.
97
   * This function then uses the returned html to get the source of the embedded content and sets the
98
   * embeddedURL state to that new source.
99
   *
100
   * @params url: The URL of the video to be embedded
101
   * @params oembedUrl: The URL endpoint to use to get the embedded representation of the video URL from
102
   */
103
  configureOEmbedPreview = (url, oembedUrl) => {
×
104
    // Request is of the form <oembedUrl>?format=json&url=<url>
105
    // For more information about the format of this request see https://oembed.com/
106
    const requestData = {format: "json", url: url};
×
107
    const queryString = new URLSearchParams(requestData).toString();
×
108
    const requestUrl = `${oembedUrl}?${queryString}`;
×
109
    fetch(requestUrl, {
×
110
      headers: {
111
        Accept: "application/json",
112
      },
113
    })
114
      .then(response => {
115
        if (response.ok) {
×
116
          return response.json();
×
117
        }
118
      })
119
      .then(res => {
120
        const match = res.html.match(/src="(\S+)"/);
×
121
        if (match.length === 2) {
×
122
          this.setState({
×
123
            embeddedURL: match[1],
124
          });
125
        }
126
      });
127
  };
128

129
  renderPreviewDisplay = () => {
×
130
    const errorMessage = message => {
×
131
      return <div className="url-message-display">{message}</div>;
×
132
    };
133
    if (this.state.embeddedURL !== "") {
×
134
      return (
×
135
        <iframe className="url-display" src={this.state.embeddedURL} allowFullScreen>
136
          {errorMessage(I18n.t("submissions.url_preview_error"))}
137
        </iframe>
138
      );
139
    } else if (!this.isValidURL(this.state.externalURL)) {
×
140
      return errorMessage(I18n.t("submissions.invalid_url", {item: `"${this.state.externalURL}"`}));
×
141
    } else {
142
      return errorMessage(I18n.t("submissions.url_preview_error"));
×
143
    }
144
  };
145

146
  render() {
147
    return (
×
148
      <div className="url-container">
149
        <div className="link-display">
150
          {/* Make Invalid URLs unclickable */}
151
          {!this.isValidURL(this.state.externalURL) ? (
×
152
            this.state.externalURL
153
          ) : (
154
            <a href={this.state.externalURL} target="_blank">
155
              {this.state.externalURL}
156
            </a>
157
          )}
158
        </div>
159
        <div className="display-area">{this.renderPreviewDisplay()}</div>
160
      </div>
161
    );
162
  }
163
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc